docs(changeset): feat(error-manager): implement centralized error reporting system
This commit is contained in:
5
.changeset/social-lamps-learn.md
Normal file
5
.changeset/social-lamps-learn.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@warkypublic/oranguru': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(error-manager): implement centralized error reporting system
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
"@changesets/cli": "^2.29.8",
|
"@changesets/cli": "^2.29.8",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@microsoft/api-extractor": "^7.56.0",
|
"@microsoft/api-extractor": "^7.56.0",
|
||||||
|
"@sentry/react": "^10.38.0",
|
||||||
"@storybook/react-vite": "^10.2.3",
|
"@storybook/react-vite": "^10.2.3",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
|
|||||||
67
pnpm-lock.yaml
generated
67
pnpm-lock.yaml
generated
@@ -72,6 +72,9 @@ importers:
|
|||||||
'@microsoft/api-extractor':
|
'@microsoft/api-extractor':
|
||||||
specifier: ^7.56.0
|
specifier: ^7.56.0
|
||||||
version: 7.56.0(@types/node@25.2.0)
|
version: 7.56.0(@types/node@25.2.0)
|
||||||
|
'@sentry/react':
|
||||||
|
specifier: ^10.38.0
|
||||||
|
version: 10.38.0(react@19.2.4)
|
||||||
'@storybook/react-vite':
|
'@storybook/react-vite':
|
||||||
specifier: ^10.2.3
|
specifier: ^10.2.3
|
||||||
version: 10.2.3(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.50.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.0)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
version: 10.2.3(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.50.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.0)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
@@ -964,6 +967,36 @@ packages:
|
|||||||
'@rushstack/ts-command-line@5.1.7':
|
'@rushstack/ts-command-line@5.1.7':
|
||||||
resolution: {integrity: sha512-Ugwl6flarZcL2nqH5IXFYk3UR3mBVDsVFlCQW/Oaqidvdb/5Ota6b/Z3JXWIdqV3rOR2/JrYoAHanWF5rgenXA==}
|
resolution: {integrity: sha512-Ugwl6flarZcL2nqH5IXFYk3UR3mBVDsVFlCQW/Oaqidvdb/5Ota6b/Z3JXWIdqV3rOR2/JrYoAHanWF5rgenXA==}
|
||||||
|
|
||||||
|
'@sentry-internal/browser-utils@10.38.0':
|
||||||
|
resolution: {integrity: sha512-UOJtYmdcxHCcV0NPfXFff/a95iXl/E0EhuQ1y0uE0BuZDMupWSF5t2BgC4HaE5Aw3RTjDF3XkSHWoIF6ohy7eA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry-internal/feedback@10.38.0':
|
||||||
|
resolution: {integrity: sha512-JXneg9zRftyfy1Fyfc39bBlF/Qd8g4UDublFFkVvdc1S6JQPlK+P6q22DKz3Pc8w3ySby+xlIq/eTu9Pzqi4KA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry-internal/replay-canvas@10.38.0':
|
||||||
|
resolution: {integrity: sha512-OXWM9jEqNYh4VTvrMu7v+z1anz+QKQ/fZXIZdsO7JTT2lGNZe58UUMeoq386M+Saxen8F9SUH7yTORy/8KI5qw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry-internal/replay@10.38.0':
|
||||||
|
resolution: {integrity: sha512-YWIkL6/dnaiQyFiZXJ/nN+NXGv/15z45ia86bE/TMq01CubX/DUOilgsFz0pk2v/pg3tp/U2MskLO9Hz0cnqeg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry/browser@10.38.0':
|
||||||
|
resolution: {integrity: sha512-3phzp1YX4wcQr9mocGWKbjv0jwtuoDBv7+Y6Yfrys/kwyaL84mDLjjQhRf4gL5SX7JdYkhBp4WaiNlR0UC4kTA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry/core@10.38.0':
|
||||||
|
resolution: {integrity: sha512-1pubWDZE5y5HZEPMAZERP4fVl2NH3Ihp1A+vMoVkb3Qc66Diqj1WierAnStlZP7tCx0TBa0dK85GTW/ZFYyB9g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry/react@10.38.0':
|
||||||
|
resolution: {integrity: sha512-3UiKo6QsqTyPGUt0XWRY9KLaxc/cs6Kz4vlldBSOXEL6qPDL/EfpwNJT61osRo81VFWu8pKu7ZY2bvLPryrnBQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.14.0 || 17.x || 18.x || 19.x
|
||||||
|
|
||||||
'@sinclair/typebox@0.27.8':
|
'@sinclair/typebox@0.27.8':
|
||||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||||
|
|
||||||
@@ -4780,6 +4813,40 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
|
|
||||||
|
'@sentry-internal/browser-utils@10.38.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/core': 10.38.0
|
||||||
|
|
||||||
|
'@sentry-internal/feedback@10.38.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/core': 10.38.0
|
||||||
|
|
||||||
|
'@sentry-internal/replay-canvas@10.38.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry-internal/replay': 10.38.0
|
||||||
|
'@sentry/core': 10.38.0
|
||||||
|
|
||||||
|
'@sentry-internal/replay@10.38.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry-internal/browser-utils': 10.38.0
|
||||||
|
'@sentry/core': 10.38.0
|
||||||
|
|
||||||
|
'@sentry/browser@10.38.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry-internal/browser-utils': 10.38.0
|
||||||
|
'@sentry-internal/feedback': 10.38.0
|
||||||
|
'@sentry-internal/replay': 10.38.0
|
||||||
|
'@sentry-internal/replay-canvas': 10.38.0
|
||||||
|
'@sentry/core': 10.38.0
|
||||||
|
|
||||||
|
'@sentry/core@10.38.0': {}
|
||||||
|
|
||||||
|
'@sentry/react@10.38.0(react@19.2.4)':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/browser': 10.38.0
|
||||||
|
'@sentry/core': 10.38.0
|
||||||
|
react: 19.2.4
|
||||||
|
|
||||||
'@sinclair/typebox@0.27.8': {}
|
'@sinclair/typebox@0.27.8': {}
|
||||||
|
|
||||||
'@standard-schema/spec@1.0.0': {}
|
'@standard-schema/spec@1.0.0': {}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React, { type PropsWithChildren } from 'react';
|
import React, { type PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
import errorManager from './ErrorManager';
|
||||||
|
|
||||||
interface ErrorBoundaryProps extends PropsWithChildren {
|
interface ErrorBoundaryProps extends PropsWithChildren {
|
||||||
namespace?: string;
|
namespace?: string;
|
||||||
onReportClick?: () => void;
|
onReportClick?: () => void;
|
||||||
@@ -43,7 +46,12 @@ export class ReactBasicErrorBoundary extends React.PureComponent<
|
|||||||
errorInfo,
|
errorInfo,
|
||||||
try: false,
|
try: false,
|
||||||
});
|
});
|
||||||
// You can also log error messages to an error reporting service here
|
|
||||||
|
// Report error to error manager (Sentry, custom API, etc.)
|
||||||
|
errorManager.reportError(error, errorInfo, {
|
||||||
|
componentStack: errorInfo?.componentStack,
|
||||||
|
namespace: this.props.namespace,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Button, Code, Collapse, Group, Paper, rem, Text } from '@mantine/core';
|
import { Button, Code, Collapse, Group, Paper, rem, Text } from '@mantine/core';
|
||||||
import { IconExclamationCircle } from '@tabler/icons-react';
|
import { IconExclamationCircle } from '@tabler/icons-react';
|
||||||
import React, { type PropsWithChildren } from 'react';
|
import React, { type PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
import errorManager from './ErrorManager';
|
||||||
|
|
||||||
let ErrorBoundaryOptions = {
|
let ErrorBoundaryOptions = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
onError: undefined,
|
onError: undefined,
|
||||||
@@ -68,7 +71,12 @@ export class ReactErrorBoundary extends React.Component<ErrorBoundaryProps, Erro
|
|||||||
if (typeof GetErrorBoundaryOptions()?.onError === 'function') {
|
if (typeof GetErrorBoundaryOptions()?.onError === 'function') {
|
||||||
GetErrorBoundaryOptions()?.onError?.(error, errorInfo);
|
GetErrorBoundaryOptions()?.onError?.(error, errorInfo);
|
||||||
}
|
}
|
||||||
// You can also log error messages to an error reporting service here
|
|
||||||
|
// Report error to error manager (Sentry, custom API, etc.)
|
||||||
|
errorManager.reportError(error, errorInfo, {
|
||||||
|
componentStack: errorInfo?.componentStack,
|
||||||
|
namespace: this.props.namespace,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -202,6 +210,19 @@ export class ReactErrorBoundary extends React.Component<ErrorBoundaryProps, Erro
|
|||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manually report error if user clicks report button
|
||||||
|
if (this.state.error && this.state.errorInfo) {
|
||||||
|
await errorManager.reportError(this.state.error, this.state.errorInfo, {
|
||||||
|
componentStack: this.state.errorInfo?.componentStack,
|
||||||
|
namespace: this.props.namespace,
|
||||||
|
tags: { reportedBy: 'user' },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState(() => ({
|
||||||
|
reported: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
|||||||
166
src/ErrorBoundary/ErrorManager.README.md
Normal file
166
src/ErrorBoundary/ErrorManager.README.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# ErrorManager
|
||||||
|
|
||||||
|
Centralized error reporting for ErrorBoundary components.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Sentry Integration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { errorManager } from './ErrorBoundary';
|
||||||
|
|
||||||
|
errorManager.configure({
|
||||||
|
enabled: true,
|
||||||
|
sentry: {
|
||||||
|
dsn: 'https://your-sentry-dsn@sentry.io/project-id',
|
||||||
|
environment: 'production',
|
||||||
|
release: '1.0.0',
|
||||||
|
sampleRate: 1.0,
|
||||||
|
ignoreErrors: ['ResizeObserver loop limit exceeded'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom API Integration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
errorManager.configure({
|
||||||
|
enabled: true,
|
||||||
|
customAPI: {
|
||||||
|
endpoint: 'https://api.yourapp.com/errors',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer token',
|
||||||
|
},
|
||||||
|
transformPayload: (report) => ({
|
||||||
|
message: report.error.message,
|
||||||
|
stack: report.error.stack,
|
||||||
|
level: report.severity,
|
||||||
|
timestamp: report.timestamp,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Reporter
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
errorManager.configure({
|
||||||
|
enabled: true,
|
||||||
|
reporters: [
|
||||||
|
{
|
||||||
|
name: 'CustomLogger',
|
||||||
|
isEnabled: () => true,
|
||||||
|
captureError: async (report) => {
|
||||||
|
console.error('Error:', report.error);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Reporters
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
errorManager.configure({
|
||||||
|
enabled: true,
|
||||||
|
sentry: { dsn: 'your-dsn' },
|
||||||
|
customAPI: { endpoint: 'your-endpoint' },
|
||||||
|
reporters: [customReporter],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
### beforeReport
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
errorManager.configure({
|
||||||
|
beforeReport: (report) => {
|
||||||
|
// Filter errors
|
||||||
|
if (report.error.message.includes('ResizeObserver')) {
|
||||||
|
return null; // Skip reporting
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrich with user data
|
||||||
|
report.context = {
|
||||||
|
...report.context,
|
||||||
|
user: { id: getCurrentUserId() },
|
||||||
|
tags: { feature: 'checkout' },
|
||||||
|
};
|
||||||
|
|
||||||
|
return report;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### onReportSuccess / onReportFailure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
errorManager.configure({
|
||||||
|
onReportSuccess: (report) => {
|
||||||
|
console.log('Error reported successfully');
|
||||||
|
},
|
||||||
|
onReportFailure: (error, report) => {
|
||||||
|
console.error('Failed to report error:', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Reporting
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
riskyOperation();
|
||||||
|
} catch (error) {
|
||||||
|
await errorManager.reportError(error as Error, null, {
|
||||||
|
namespace: 'checkout',
|
||||||
|
tags: { step: 'payment' },
|
||||||
|
extra: { orderId: '123' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disable/Enable
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Disable reporting
|
||||||
|
errorManager.configure({ enabled: false });
|
||||||
|
|
||||||
|
// Enable reporting
|
||||||
|
errorManager.configure({ enabled: true });
|
||||||
|
```
|
||||||
|
|
||||||
|
## ErrorBoundary Integration
|
||||||
|
|
||||||
|
Automatic - errors caught by `ReactErrorBoundary` or `ReactBasicErrorBoundary` are automatically reported.
|
||||||
|
|
||||||
|
Manual report button in `ReactErrorBoundary` UI also sends to ErrorManager.
|
||||||
|
|
||||||
|
## Install Sentry (optional)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @sentry/react
|
||||||
|
```
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type ErrorSeverity = 'fatal' | 'error' | 'warning' | 'info' | 'debug';
|
||||||
|
|
||||||
|
interface ErrorContext {
|
||||||
|
namespace?: string;
|
||||||
|
componentStack?: string;
|
||||||
|
user?: Record<string, any>;
|
||||||
|
tags?: Record<string, string>;
|
||||||
|
extra?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorReport {
|
||||||
|
error: Error;
|
||||||
|
errorInfo?: any;
|
||||||
|
severity?: ErrorSeverity;
|
||||||
|
context?: ErrorContext;
|
||||||
|
timestamp?: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
194
src/ErrorBoundary/ErrorManager.ts
Normal file
194
src/ErrorBoundary/ErrorManager.ts
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import type {
|
||||||
|
CustomAPIConfig,
|
||||||
|
ErrorManagerConfig,
|
||||||
|
ErrorReport,
|
||||||
|
ErrorReporter,
|
||||||
|
SentryConfig,
|
||||||
|
} from './ErrorManager.types';
|
||||||
|
|
||||||
|
class ErrorManager {
|
||||||
|
private config: ErrorManagerConfig = { enabled: true };
|
||||||
|
private reporters: ErrorReporter[] = [];
|
||||||
|
private sentryInstance: any = null;
|
||||||
|
|
||||||
|
configure(config: ErrorManagerConfig) {
|
||||||
|
this.config = { ...this.config, ...config };
|
||||||
|
this.reporters = [];
|
||||||
|
|
||||||
|
if (config.sentry) {
|
||||||
|
this.setupSentry(config.sentry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.customAPI) {
|
||||||
|
this.setupCustomAPI(config.customAPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.reporters) {
|
||||||
|
this.reporters.push(...config.reporters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.reporters = [];
|
||||||
|
this.sentryInstance = null;
|
||||||
|
this.config = { enabled: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
getReporters(): ErrorReporter[] {
|
||||||
|
return [...this.reporters];
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnabled(): boolean {
|
||||||
|
return Boolean(this.config.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
async reportError(
|
||||||
|
error: Error,
|
||||||
|
errorInfo?: any,
|
||||||
|
context?: ErrorReport['context']
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.config.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let report: ErrorReport = {
|
||||||
|
context,
|
||||||
|
error,
|
||||||
|
errorInfo,
|
||||||
|
severity: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.config.beforeReport) {
|
||||||
|
const modifiedReport = this.config.beforeReport(report);
|
||||||
|
if (!modifiedReport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
report = modifiedReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportPromises = this.reporters
|
||||||
|
.filter((reporter) => reporter.isEnabled())
|
||||||
|
.map(async (reporter) => {
|
||||||
|
try {
|
||||||
|
await reporter.captureError(report);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error reporter "${reporter.name}" failed:`, error);
|
||||||
|
if (this.config.onReportFailure) {
|
||||||
|
this.config.onReportFailure(error as Error, report);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(reportPromises);
|
||||||
|
if (this.config.onReportSuccess) {
|
||||||
|
this.config.onReportSuccess(report);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reporting failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupCustomAPI(config: CustomAPIConfig) {
|
||||||
|
const customAPIReporter: ErrorReporter = {
|
||||||
|
captureError: async (report: ErrorReport) => {
|
||||||
|
try {
|
||||||
|
const payload = config.transformPayload
|
||||||
|
? config.transformPayload(report)
|
||||||
|
: {
|
||||||
|
context: report.context,
|
||||||
|
message: report.error.message,
|
||||||
|
severity: report.severity || 'error',
|
||||||
|
stack: report.error.stack,
|
||||||
|
timestamp: report.timestamp || Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(config.endpoint, {
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...config.headers,
|
||||||
|
},
|
||||||
|
method: config.method || 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`API request failed: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to send error to custom API:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isEnabled: () => Boolean(config.endpoint),
|
||||||
|
name: 'CustomAPI',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.reporters.push(customAPIReporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupSentry(config: SentryConfig) {
|
||||||
|
const sentryReporter: ErrorReporter = {
|
||||||
|
captureError: async (report: ErrorReport) => {
|
||||||
|
if (!this.sentryInstance) {
|
||||||
|
try {
|
||||||
|
const Sentry = await import('@sentry/react');
|
||||||
|
Sentry.init({
|
||||||
|
beforeSend: config.beforeSend,
|
||||||
|
dsn: config.dsn,
|
||||||
|
environment: config.environment,
|
||||||
|
ignoreErrors: config.ignoreErrors,
|
||||||
|
release: config.release,
|
||||||
|
tracesSampleRate: config.sampleRate || 1.0,
|
||||||
|
});
|
||||||
|
this.sentryInstance = Sentry;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize Sentry:', error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
this.sentryInstance.withScope((scope: any) => {
|
||||||
|
if (report.severity) {
|
||||||
|
scope.setLevel(report.severity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.context?.namespace) {
|
||||||
|
scope.setTag('namespace', report.context.namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.context?.tags) {
|
||||||
|
Object.entries(report.context.tags).forEach(([key, value]) => {
|
||||||
|
scope.setTag(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.context?.user) {
|
||||||
|
scope.setUser(report.context.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.context?.extra) {
|
||||||
|
scope.setExtras(report.context.extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.context?.componentStack) {
|
||||||
|
scope.setContext('react', {
|
||||||
|
componentStack: report.context.componentStack,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sentryInstance.captureException(report.error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isEnabled: () => Boolean(this.sentryInstance),
|
||||||
|
name: 'Sentry',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.reporters.push(sentryReporter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const errorManager = new ErrorManager();
|
||||||
|
|
||||||
|
export default errorManager;
|
||||||
50
src/ErrorBoundary/ErrorManager.types.ts
Normal file
50
src/ErrorBoundary/ErrorManager.types.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
export interface CustomAPIConfig {
|
||||||
|
endpoint: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
method?: 'POST' | 'PUT';
|
||||||
|
transformPayload?: (report: ErrorReport) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorContext {
|
||||||
|
componentStack?: string;
|
||||||
|
extra?: Record<string, any>;
|
||||||
|
namespace?: string;
|
||||||
|
tags?: Record<string, string>;
|
||||||
|
user?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorManagerConfig {
|
||||||
|
beforeReport?: (report: ErrorReport) => ErrorReport | null;
|
||||||
|
customAPI?: CustomAPIConfig;
|
||||||
|
enabled?: boolean;
|
||||||
|
onReportFailure?: (error: Error, report: ErrorReport) => void;
|
||||||
|
onReportSuccess?: (report: ErrorReport) => void;
|
||||||
|
reporters?: ErrorReporter[];
|
||||||
|
sentry?: SentryConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorReport {
|
||||||
|
context?: ErrorContext;
|
||||||
|
error: Error;
|
||||||
|
errorInfo?: any;
|
||||||
|
severity?: ErrorSeverity;
|
||||||
|
timestamp?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorReporter {
|
||||||
|
captureError: (report: ErrorReport) => Promise<void> | void;
|
||||||
|
isEnabled: () => boolean;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ErrorSeverity = 'debug' | 'error' | 'fatal' | 'info' | 'warning';
|
||||||
|
|
||||||
|
export interface SentryConfig {
|
||||||
|
beforeSend?: (event: any) => any | null;
|
||||||
|
dsn: string;
|
||||||
|
environment?: string;
|
||||||
|
ignoreErrors?: string[];
|
||||||
|
release?: string;
|
||||||
|
sampleRate?: number;
|
||||||
|
}
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
export { default as ReactBasicErrorBoundary } from './BasicErrorBoundary';
|
export { default as ReactBasicErrorBoundary } from './BasicErrorBoundary';
|
||||||
export { default as ReactErrorBoundary } from './ErrorBoundary';
|
export { default as ReactErrorBoundary } from './ErrorBoundary';
|
||||||
|
export { default as errorManager } from './ErrorManager';
|
||||||
|
export * from './ErrorManager.types';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import type { FormerAPICallType } from './Former.types';
|
import type { FormerAPICallType } from './Former.types';
|
||||||
|
|
||||||
interface ResolveSpecRequest {
|
interface ResolveSpecRequest {
|
||||||
@@ -65,7 +66,7 @@ function FormerResolveSpecAPI(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data as any;
|
return data as unknown;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { fn } from 'storybook/test';
|
|||||||
|
|
||||||
import { FormTest } from './example';
|
import { FormTest } from './example';
|
||||||
|
|
||||||
const Renderable = (props: any) => {
|
const Renderable = (props: unknown) => {
|
||||||
return (
|
return (
|
||||||
<Box h="100%" mih="400px" miw="400px" w="100%">
|
<Box h="100%" mih="400px" miw="400px" w="100%">
|
||||||
<FormTest {...props} />
|
<FormTest {...props} />
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { useStoreWithEqualityFn } from 'zustand/traditional';
|
|||||||
import { createStore } from 'zustand/vanilla';
|
import { createStore } from 'zustand/vanilla';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
|
||||||
BarState,
|
BarState,
|
||||||
ExtractState,
|
ExtractState,
|
||||||
GlobalState,
|
GlobalState,
|
||||||
@@ -21,10 +20,6 @@ import type {
|
|||||||
import { loadStorage, saveStorage } from './GlobalStateStore.utils';
|
import { loadStorage, saveStorage } from './GlobalStateStore.utils';
|
||||||
|
|
||||||
const initialState: GlobalState = {
|
const initialState: GlobalState = {
|
||||||
app: {
|
|
||||||
controls: {},
|
|
||||||
environment: 'production',
|
|
||||||
},
|
|
||||||
layout: {
|
layout: {
|
||||||
bottomBar: { open: false },
|
bottomBar: { open: false },
|
||||||
leftBar: { open: false },
|
leftBar: { open: false },
|
||||||
@@ -35,10 +30,14 @@ const initialState: GlobalState = {
|
|||||||
menu: [],
|
menu: [],
|
||||||
},
|
},
|
||||||
owner: {
|
owner: {
|
||||||
|
guid: '',
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
},
|
},
|
||||||
program: {
|
program: {
|
||||||
|
controls: {},
|
||||||
|
environment: 'production',
|
||||||
|
guid: '',
|
||||||
name: '',
|
name: '',
|
||||||
slug: '',
|
slug: '',
|
||||||
},
|
},
|
||||||
@@ -47,8 +46,10 @@ const initialState: GlobalState = {
|
|||||||
authToken: '',
|
authToken: '',
|
||||||
connected: true,
|
connected: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
loggedIn: false,
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
|
guid: '',
|
||||||
username: '',
|
username: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -140,13 +141,6 @@ const createNavigationSlice = (set: SetState) => ({
|
|||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createAppSlice = (set: SetState) => ({
|
|
||||||
setApp: (updates: Partial<AppState>) =>
|
|
||||||
set((state: GlobalState) => ({
|
|
||||||
app: { ...state.app, ...updates },
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
const createComplexActions = (set: SetState, get: GetState) => ({
|
const createComplexActions = (set: SetState, get: GetState) => ({
|
||||||
fetchData: async (url?: string) => {
|
fetchData: async (url?: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -164,9 +158,9 @@ const createComplexActions = (set: SetState, get: GetState) => ({
|
|||||||
set((state: GlobalState) => ({
|
set((state: GlobalState) => ({
|
||||||
...state,
|
...state,
|
||||||
...result,
|
...result,
|
||||||
app: {
|
program: {
|
||||||
...state.app,
|
...state.program,
|
||||||
...result?.app,
|
...result?.program,
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
@@ -188,25 +182,101 @@ const createComplexActions = (set: SetState, get: GetState) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isLoggedIn: (): boolean => {
|
||||||
|
const session = get().session;
|
||||||
|
if (!session.loggedIn || !session.authToken) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (session.expiryDate && new Date(session.expiryDate) < new Date()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
login: async (authToken?: string) => {
|
login: async (authToken?: string) => {
|
||||||
|
try {
|
||||||
set((state: GlobalState) => ({
|
set((state: GlobalState) => ({
|
||||||
session: {
|
session: {
|
||||||
...state.session,
|
...state.session,
|
||||||
authToken: authToken ?? '',
|
authToken: authToken ?? '',
|
||||||
|
expiryDate: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
||||||
|
loading: true,
|
||||||
|
loggedIn: true,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
await get().fetchData();
|
await get().fetchData();
|
||||||
|
const currentState = get();
|
||||||
|
const result = await currentState.onLogin?.(currentState);
|
||||||
|
if (result) {
|
||||||
|
set((state: GlobalState) => ({
|
||||||
|
...state,
|
||||||
|
owner: result.owner ? { ...state.owner, ...result.owner } : state.owner,
|
||||||
|
program: result.program ? { ...state.program, ...result.program } : state.program,
|
||||||
|
session: result.session ? { ...state.session, ...result.session } : state.session,
|
||||||
|
user: result.user ? { ...state.user, ...result.user } : state.user,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
set((state: GlobalState) => ({
|
||||||
|
session: {
|
||||||
|
...state.session,
|
||||||
|
connected: false,
|
||||||
|
error: `Login Exception: ${String(e)}`,
|
||||||
|
loading: false,
|
||||||
|
loggedIn: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} finally {
|
||||||
|
set((state: GlobalState) => ({
|
||||||
|
session: {
|
||||||
|
...state.session,
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
|
try {
|
||||||
set((state: GlobalState) => ({
|
set((state: GlobalState) => ({
|
||||||
...initialState,
|
...initialState,
|
||||||
session: {
|
session: {
|
||||||
...initialState.session,
|
...initialState.session,
|
||||||
apiURL: state.session.apiURL,
|
apiURL: state.session.apiURL,
|
||||||
|
expiryDate: undefined,
|
||||||
|
loading: true,
|
||||||
|
loggedIn: false,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
await get().fetchData();
|
await get().fetchData();
|
||||||
|
const currentState = get();
|
||||||
|
const result = await currentState.onLogout?.(currentState);
|
||||||
|
if (result) {
|
||||||
|
set((state: GlobalState) => ({
|
||||||
|
...state,
|
||||||
|
owner: result.owner ? { ...state.owner, ...result.owner } : state.owner,
|
||||||
|
program: result.program ? { ...state.program, ...result.program } : state.program,
|
||||||
|
session: result.session ? { ...state.session, ...result.session } : state.session,
|
||||||
|
user: result.user ? { ...state.user, ...result.user } : state.user,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
set((state: GlobalState) => ({
|
||||||
|
session: {
|
||||||
|
...state.session,
|
||||||
|
connected: false,
|
||||||
|
error: `Logout Exception: ${String(e)}`,
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} finally {
|
||||||
|
set((state: GlobalState) => ({
|
||||||
|
session: {
|
||||||
|
...state.session,
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -218,7 +288,6 @@ const GlobalStateStore = createStore<GlobalStateStoreType>((set, get) => ({
|
|||||||
...createUserSlice(set),
|
...createUserSlice(set),
|
||||||
...createLayoutSlice(set),
|
...createLayoutSlice(set),
|
||||||
...createNavigationSlice(set),
|
...createNavigationSlice(set),
|
||||||
...createAppSlice(set),
|
|
||||||
...createComplexActions(set, get),
|
...createComplexActions(set, get),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -267,6 +336,10 @@ const getAuthToken = (): string => {
|
|||||||
return GlobalStateStore.getState().session.authToken;
|
return GlobalStateStore.getState().session.authToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isLoggedIn = (): boolean => {
|
||||||
|
return GlobalStateStore.getState().isLoggedIn();
|
||||||
|
};
|
||||||
|
|
||||||
const setAuthToken = (token: string) => {
|
const setAuthToken = (token: string) => {
|
||||||
GlobalStateStore.getState().setAuthToken(token);
|
GlobalStateStore.getState().setAuthToken(token);
|
||||||
};
|
};
|
||||||
@@ -275,4 +348,4 @@ const GetGlobalState = (): GlobalStateStoreType => {
|
|||||||
return GlobalStateStore.getState();
|
return GlobalStateStore.getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getApiURL, getAuthToken, GetGlobalState, GlobalStateStore, setApiURL, setAuthToken, useGlobalStateStore };
|
export { getApiURL, getAuthToken, GetGlobalState, GlobalStateStore, isLoggedIn, setApiURL, setAuthToken, useGlobalStateStore };
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
interface AppState {
|
|
||||||
controls?: Record<string, any>;
|
|
||||||
environment: 'development' | 'production';
|
|
||||||
globals?: Record<string, any>;
|
|
||||||
updatedAt?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BarState {
|
interface BarState {
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
@@ -14,7 +8,6 @@ interface BarState {
|
|||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
render?: () => React.ReactNode;
|
render?: () => React.ReactNode;
|
||||||
size?: number;
|
size?: number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatabaseDetail = {
|
type DatabaseDetail = {
|
||||||
@@ -25,7 +18,6 @@ type DatabaseDetail = {
|
|||||||
type ExtractState<S> = S extends { getState: () => infer X } ? X : never;
|
type ExtractState<S> = S extends { getState: () => infer X } ? X : never;
|
||||||
|
|
||||||
interface GlobalState {
|
interface GlobalState {
|
||||||
app: AppState;
|
|
||||||
layout: LayoutState;
|
layout: LayoutState;
|
||||||
navigation: NavigationState;
|
navigation: NavigationState;
|
||||||
owner: OwnerState;
|
owner: OwnerState;
|
||||||
@@ -38,15 +30,19 @@ interface GlobalStateActions {
|
|||||||
// Complex actions
|
// Complex actions
|
||||||
fetchData: (url?: string) => Promise<void>;
|
fetchData: (url?: string) => Promise<void>;
|
||||||
|
|
||||||
|
isLoggedIn: () => boolean;
|
||||||
login: (authToken?: string) => Promise<void>;
|
login: (authToken?: string) => Promise<void>;
|
||||||
logout: () => Promise<void>;
|
logout: () => Promise<void>;
|
||||||
// Callback for custom fetch logic
|
// Callbacks for custom logic
|
||||||
onFetchSession?: (state: GlobalState) => Promise<Partial<GlobalState>>;
|
onFetchSession?: (state: GlobalState) => Promise<Partial<GlobalState>>;
|
||||||
|
onLogin?: (
|
||||||
|
state: GlobalState
|
||||||
|
) => Promise<Pick<GlobalState, 'owner' | 'program' | 'session' | 'user'> | void>;
|
||||||
|
onLogout?: (
|
||||||
|
state: GlobalState
|
||||||
|
) => Promise<Pick<GlobalState, 'owner' | 'program' | 'session' | 'user'> | void>;
|
||||||
setApiURL: (url: string) => void;
|
setApiURL: (url: string) => void;
|
||||||
|
|
||||||
// App actions
|
|
||||||
setApp: (updates: Partial<AppState>) => void;
|
|
||||||
|
|
||||||
setAuthToken: (token: string) => void;
|
setAuthToken: (token: string) => void;
|
||||||
setBottomBar: (updates: Partial<BarState>) => void;
|
setBottomBar: (updates: Partial<BarState>) => void;
|
||||||
setCurrentPage: (page: PageInfo) => void;
|
setCurrentPage: (page: PageInfo) => void;
|
||||||
@@ -101,6 +97,7 @@ interface NavigationState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface OwnerState {
|
interface OwnerState {
|
||||||
|
guid?: string;
|
||||||
id: number;
|
id: number;
|
||||||
logo?: string;
|
logo?: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -118,24 +115,19 @@ type PageInfo = {
|
|||||||
interface ProgramState {
|
interface ProgramState {
|
||||||
backendVersion?: string;
|
backendVersion?: string;
|
||||||
bigLogo?: string;
|
bigLogo?: string;
|
||||||
|
controls?: Record<string, any>;
|
||||||
database?: DatabaseDetail;
|
database?: DatabaseDetail;
|
||||||
databaseVersion?: string;
|
databaseVersion?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
environment: 'development' | 'production';
|
||||||
|
globals?: Record<string, any>;
|
||||||
|
guid?: string;
|
||||||
logo?: string;
|
logo?: string;
|
||||||
meta?: Record<string, any>;
|
meta?: Record<string, any>;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
version?: string;
|
updatedAt?: string;
|
||||||
}
|
|
||||||
|
|
||||||
interface ProgramWrapperProps {
|
|
||||||
apiURL?: string;
|
|
||||||
children: React.ReactNode | React.ReactNode[];
|
|
||||||
debugMode?: boolean;
|
|
||||||
fallback?: React.ReactNode | React.ReactNode[];
|
|
||||||
renderFallback?: boolean;
|
|
||||||
testMode?: boolean;
|
|
||||||
version?: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,8 +136,10 @@ interface SessionState {
|
|||||||
authToken: string;
|
authToken: string;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
expiryDate?: string;
|
||||||
isSecurity?: boolean;
|
isSecurity?: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
loggedIn: boolean;
|
||||||
meta?: Record<string, any>;
|
meta?: Record<string, any>;
|
||||||
parameters?: Record<string, any>;
|
parameters?: Record<string, any>;
|
||||||
}
|
}
|
||||||
@@ -169,7 +163,6 @@ interface UserState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
AppState,
|
|
||||||
BarState,
|
BarState,
|
||||||
ExtractState,
|
ExtractState,
|
||||||
GlobalState,
|
GlobalState,
|
||||||
@@ -181,7 +174,6 @@ export type {
|
|||||||
OwnerState,
|
OwnerState,
|
||||||
PageInfo,
|
PageInfo,
|
||||||
ProgramState,
|
ProgramState,
|
||||||
ProgramWrapperProps,
|
|
||||||
SessionState,
|
SessionState,
|
||||||
ThemeSettings,
|
ThemeSettings,
|
||||||
UserState,
|
UserState,
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
import {
|
||||||
|
createContext,
|
||||||
|
type ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import type { GlobalStateStoreType } from './GlobalStateStore.types';
|
import type { GlobalState, GlobalStateStoreType, ProgramState } from './GlobalStateStore.types';
|
||||||
|
|
||||||
import { GetGlobalState, GlobalStateStore } from './GlobalStateStore';
|
import { GetGlobalState, GlobalStateStore } from './GlobalStateStore';
|
||||||
|
|
||||||
|
|
||||||
interface GlobalStateStoreContextValue {
|
interface GlobalStateStoreContextValue {
|
||||||
fetchData: (url?: string) => Promise<void>;
|
fetchData: (url?: string) => Promise<void>;
|
||||||
getState: () => GlobalStateStoreType;
|
getState: () => GlobalStateStoreType;
|
||||||
@@ -13,12 +20,15 @@ interface GlobalStateStoreContextValue {
|
|||||||
|
|
||||||
const GlobalStateStoreContext = createContext<GlobalStateStoreContextValue | null>(null);
|
const GlobalStateStoreContext = createContext<GlobalStateStoreContextValue | null>(null);
|
||||||
|
|
||||||
|
|
||||||
interface GlobalStateStoreProviderProps {
|
interface GlobalStateStoreProviderProps {
|
||||||
apiURL?: string;
|
apiURL?: string;
|
||||||
autoFetch?: boolean;
|
autoFetch?: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
fetchOnMount?: boolean;
|
fetchOnMount?: boolean;
|
||||||
|
onFetchSession?: (state: GlobalState) => Promise<Partial<GlobalState>>;
|
||||||
|
onLogin?: (state: GlobalState) => Promise<void>;
|
||||||
|
onLogout?: (state: GlobalState) => Promise<void>;
|
||||||
|
program?: Partial<ProgramState>;
|
||||||
throttleMs?: number;
|
throttleMs?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,13 +37,16 @@ export function GlobalStateStoreProvider({
|
|||||||
autoFetch = true,
|
autoFetch = true,
|
||||||
children,
|
children,
|
||||||
fetchOnMount = true,
|
fetchOnMount = true,
|
||||||
|
onFetchSession,
|
||||||
|
onLogin,
|
||||||
|
onLogout,
|
||||||
|
program,
|
||||||
throttleMs = 0,
|
throttleMs = 0,
|
||||||
}: GlobalStateStoreProviderProps) {
|
}: GlobalStateStoreProviderProps) {
|
||||||
const lastFetchTime = useRef<number>(0);
|
const lastFetchTime = useRef<number>(0);
|
||||||
const fetchInProgress = useRef<boolean>(false);
|
const fetchInProgress = useRef<boolean>(false);
|
||||||
const mounted = useRef<boolean>(false);
|
const mounted = useRef<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
const throttledFetch = useCallback(
|
const throttledFetch = useCallback(
|
||||||
async (url?: string) => {
|
async (url?: string) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -68,6 +81,30 @@ export function GlobalStateStoreProvider({
|
|||||||
}
|
}
|
||||||
}, [apiURL]);
|
}, [apiURL]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (program) {
|
||||||
|
GlobalStateStore.getState().setProgram(program);
|
||||||
|
}
|
||||||
|
}, [program]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onFetchSession) {
|
||||||
|
GlobalStateStore.setState({ onFetchSession });
|
||||||
|
}
|
||||||
|
}, [onFetchSession]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onLogin) {
|
||||||
|
GlobalStateStore.setState({ onLogin });
|
||||||
|
}
|
||||||
|
}, [onLogin]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onLogout) {
|
||||||
|
GlobalStateStore.setState({ onLogout });
|
||||||
|
}
|
||||||
|
}, [onLogout]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!mounted.current) {
|
if (!mounted.current) {
|
||||||
mounted.current = true;
|
mounted.current = true;
|
||||||
@@ -88,12 +125,8 @@ export function GlobalStateStoreProvider({
|
|||||||
};
|
};
|
||||||
}, [throttledFetch, refetch]);
|
}, [throttledFetch, refetch]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GlobalStateStoreContext.Provider value={context}>
|
<GlobalStateStoreContext.Provider value={context}>{children}</GlobalStateStoreContext.Provider>
|
||||||
{children}
|
|
||||||
</GlobalStateStoreContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Button, Checkbox, Divider, Group, Stack, TagsInput, TextInput } from '@mantine/core';
|
import { Button, Checkbox, Divider, Group, Stack, TagsInput, TextInput } from '@mantine/core';
|
||||||
import { useLocalStorage } from '@mantine/hooks';
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
@@ -22,9 +23,20 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
const [selectRow, setSelectRow] = useState<string | undefined>('');
|
const [selectRow, setSelectRow] = useState<string | undefined>('');
|
||||||
const [values, setValues] = useState<Array<Record<string, any>>>([]);
|
const [values, setValues] = useState<Array<Record<string, any>>>([]);
|
||||||
const [search, setSearch] = useState<string>('');
|
const [search, setSearch] = useState<string>('');
|
||||||
const [formProps, setFormProps] = useState<{ onChange?: any; onClose?: any; opened: boolean; request: any; title?: string; values: any; } | null>({
|
const [formProps, setFormProps] = useState<{
|
||||||
onChange: (_request: string, data: any) => { ref.current?.refresh({ value: data }); },
|
onChange?: any;
|
||||||
onClose: () => { setFormProps((cv) => ({ ...cv, opened: false, request: null, values: null })) },
|
onClose?: any;
|
||||||
|
opened: boolean;
|
||||||
|
request: any;
|
||||||
|
title?: string;
|
||||||
|
values: any;
|
||||||
|
} | null>({
|
||||||
|
onChange: (_request: string, data: any) => {
|
||||||
|
ref.current?.refresh({ value: data });
|
||||||
|
},
|
||||||
|
onClose: () => {
|
||||||
|
setFormProps((cv) => ({ ...cv, opened: false, request: null, values: null }));
|
||||||
|
},
|
||||||
opened: false,
|
opened: false,
|
||||||
request: null,
|
request: null,
|
||||||
values: null,
|
values: null,
|
||||||
@@ -33,7 +45,8 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
const columns: GridlerColumns = [
|
const columns: GridlerColumns = [
|
||||||
{
|
{
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
const process = `${row?.cql2?.length > 0
|
const process = `${
|
||||||
|
row?.cql2?.length > 0
|
||||||
? '🔖'
|
? '🔖'
|
||||||
: row?.cql1?.length > 0
|
: row?.cql1?.length > 0
|
||||||
? '📕'
|
? '📕'
|
||||||
@@ -140,14 +153,14 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
descriptionField={'process'}
|
descriptionField={'process'}
|
||||||
onRequestForm={(request, data) => {
|
onRequestForm={(request, data) => {
|
||||||
setFormProps((cv) => {
|
setFormProps((cv) => {
|
||||||
return {...cv, opened: true, request: request as any, values: data as any}
|
return { ...cv, opened: true, request: request as any, values: data as any };
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Gridler>
|
</Gridler>
|
||||||
<FormerDialog
|
<FormerDialog
|
||||||
former={{
|
former={{
|
||||||
request: formProps?.request ?? "insert",
|
request: formProps?.request ?? 'insert',
|
||||||
values: formProps?.values,
|
values: formProps?.values,
|
||||||
}}
|
}}
|
||||||
onClose={formProps?.onClose}
|
onClose={formProps?.onClose}
|
||||||
@@ -158,7 +171,7 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
<TextInputCtrl label="Process Name" name="process" />
|
<TextInputCtrl label="Process Name" name="process" />
|
||||||
<NumberInputCtrl label="Sequence" name="sequence" />
|
<NumberInputCtrl label="Sequence" name="sequence" />
|
||||||
<InlineWrapper label="Type" promptWidth={200}>
|
<InlineWrapper label="Type" promptWidth={200}>
|
||||||
<NativeSelectCtrl data={["trigger","function","view"]} name="type"/>
|
<NativeSelectCtrl data={['trigger', 'function', 'view']} name="type" />
|
||||||
</InlineWrapper>
|
</InlineWrapper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</FormerDialog>
|
</FormerDialog>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
//@ts-nocheck
|
//@ts-nocheck
|
||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
//@ts-nocheck
|
//@ts-nocheck
|
||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
import {b64EncodeUnicode} from '@warkypublic/artemis-kit/base64'
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const TOKEN_KEY = 'gridler_golang_restapi_v2_token'
|
import { b64EncodeUnicode } from '@warkypublic/artemis-kit/base64';
|
||||||
|
const TOKEN_KEY = 'gridler_golang_restapi_v2_token';
|
||||||
|
|
||||||
export type APIOptionsType = {
|
export type APIOptionsType = {
|
||||||
autocreate?: boolean
|
autocreate?: boolean;
|
||||||
autoref?: boolean
|
autoref?: boolean;
|
||||||
baseurl?: string
|
baseurl?: string;
|
||||||
getAPIProvider?: () => { provider: string; providerKey: string }
|
getAPIProvider?: () => { provider: string; providerKey: string };
|
||||||
getAuthToken?: () => string
|
getAuthToken?: () => string;
|
||||||
operations?: Array<FetchAPIOperation>
|
operations?: Array<FetchAPIOperation>;
|
||||||
postfix?: string
|
postfix?: string;
|
||||||
prefix?: string
|
prefix?: string;
|
||||||
requestTimeoutSec?: number
|
requestTimeoutSec?: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface APIResponse {
|
export interface APIResponse {
|
||||||
errmsg: string
|
errmsg: string;
|
||||||
payload?: any
|
payload?: any;
|
||||||
retval: number
|
retval: number;
|
||||||
}
|
}
|
||||||
export interface FetchAPIOperation {
|
export interface FetchAPIOperation {
|
||||||
name?: string
|
name?: string;
|
||||||
op?: string
|
op?: string;
|
||||||
type: GoAPIHeaderTypes //x-fieldfilter
|
type: GoAPIHeaderTypes; //x-fieldfilter
|
||||||
value: string
|
value: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @description Types for the Go Rest API headers
|
* @description Types for the Go Rest API headers
|
||||||
@@ -72,28 +71,24 @@ export type GoAPIEnum =
|
|||||||
| 'simpleapi'
|
| 'simpleapi'
|
||||||
| 'skipcache'
|
| 'skipcache'
|
||||||
| 'skipcount'
|
| 'skipcount'
|
||||||
| 'sort'
|
| 'sort';
|
||||||
|
|
||||||
|
export type GoAPIHeaderKeys = `x-${GoAPIEnum}`;
|
||||||
|
|
||||||
export type GoAPIHeaderKeys = `x-${GoAPIEnum}`
|
export type GoAPIHeaderTypes = GoAPIEnum & string;
|
||||||
|
|
||||||
|
|
||||||
export type GoAPIHeaderTypes = GoAPIEnum & string
|
|
||||||
|
|
||||||
|
|
||||||
export interface GoAPIOperation {
|
export interface GoAPIOperation {
|
||||||
name?: string
|
name?: string;
|
||||||
op?: string
|
op?: string;
|
||||||
type: GoAPIHeaderTypes //x-fieldfilter
|
type: GoAPIHeaderTypes; //x-fieldfilter
|
||||||
value: string
|
value: string;
|
||||||
}
|
}
|
||||||
export interface MetaData {
|
export interface MetaData {
|
||||||
limit?: number
|
limit?: number;
|
||||||
offset?: number
|
offset?: number;
|
||||||
total?: number
|
total?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an array of objects by encoding specific values and setting headers.
|
* Builds an array of objects by encoding specific values and setting headers.
|
||||||
*
|
*
|
||||||
@@ -105,50 +100,49 @@ const buildGoAPIOperation = (
|
|||||||
ops: Array<FetchAPIOperation>,
|
ops: Array<FetchAPIOperation>,
|
||||||
headers?: Headers
|
headers?: Headers
|
||||||
): Array<FetchAPIOperation> => {
|
): Array<FetchAPIOperation> => {
|
||||||
const newops = [...ops.filter((i) => i !== undefined && i.type !== undefined)]
|
const newops = [...ops.filter((i) => i !== undefined && i.type !== undefined)];
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < newops.length; i++) {
|
for (let i = 0; i < newops.length; i++) {
|
||||||
if (!newops[i].name || newops[i].name === '') {
|
if (!newops[i].name || newops[i].name === '') {
|
||||||
newops[i].name = ''
|
newops[i].name = '';
|
||||||
}
|
}
|
||||||
if (newops[i].type === 'files' && !newops[i].value.startsWith('__')) {
|
if (newops[i].type === 'files' && !newops[i].value.startsWith('__')) {
|
||||||
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`;
|
||||||
}
|
}
|
||||||
if (newops[i].type === 'advsql' && !newops[i].value.startsWith('__')) {
|
if (newops[i].type === 'advsql' && !newops[i].value.startsWith('__')) {
|
||||||
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newops[i].type === 'custom-sql-or' && !newops[i].value.startsWith('__')) {
|
if (newops[i].type === 'custom-sql-or' && !newops[i].value.startsWith('__')) {
|
||||||
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newops[i].type === 'custom-sql-join' && !newops[i].value.startsWith('__')) {
|
if (newops[i].type === 'custom-sql-join' && !newops[i].value.startsWith('__')) {
|
||||||
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`;
|
||||||
}
|
}
|
||||||
if (newops[i].type === 'not-select-fields' && !newops[i].value.startsWith('__')) {
|
if (newops[i].type === 'not-select-fields' && !newops[i].value.startsWith('__')) {
|
||||||
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`;
|
||||||
}
|
}
|
||||||
if (newops[i].type === 'custom-sql-w' && !newops[i].value.startsWith('__')) {
|
if (newops[i].type === 'custom-sql-w' && !newops[i].value.startsWith('__')) {
|
||||||
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`;
|
||||||
}
|
}
|
||||||
if (newops[i].type === 'select-fields' && !newops[i].value.startsWith('__')) {
|
if (newops[i].type === 'select-fields' && !newops[i].value.startsWith('__')) {
|
||||||
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`;
|
||||||
}
|
}
|
||||||
if (newops[i].type === 'cql-sel' && !newops[i].value.startsWith('__')) {
|
if (newops[i].type === 'cql-sel' && !newops[i].value.startsWith('__')) {
|
||||||
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (headers) {
|
if (headers) {
|
||||||
if (!newops || newops.length === 0) {
|
if (!newops || newops.length === 0) {
|
||||||
headers.set(`x-limit`, '10')
|
headers.set(`x-limit`, '10');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newops[i].type === 'association_autoupdate') {
|
if (newops[i].type === 'association_autoupdate') {
|
||||||
headers.set(`association_autoupdate`, newops[i].value ?? '1')
|
headers.set(`association_autoupdate`, newops[i].value ?? '1');
|
||||||
}
|
}
|
||||||
if (newops[i].type === 'association_autocreate') {
|
if (newops[i].type === 'association_autocreate') {
|
||||||
headers.set(`association_autocreate`, newops[i].value ?? '1')
|
headers.set(`association_autocreate`, newops[i].value ?? '1');
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
newops[i].type === 'searchop' ||
|
newops[i].type === 'searchop' ||
|
||||||
@@ -158,20 +152,20 @@ const buildGoAPIOperation = (
|
|||||||
headers.set(
|
headers.set(
|
||||||
encodeURIComponent(`x-${newops[i].type}-${newops[i].op}-${newops[i].name}`),
|
encodeURIComponent(`x-${newops[i].type}-${newops[i].op}-${newops[i].name}`),
|
||||||
String(newops[i].value)
|
String(newops[i].value)
|
||||||
)
|
);
|
||||||
} else {
|
} else {
|
||||||
headers.set(
|
headers.set(
|
||||||
encodeURIComponent(
|
encodeURIComponent(
|
||||||
`x-${newops[i].type}${newops[i].name && newops[i].name !== '' ? '-' + newops[i].name : ''}`
|
`x-${newops[i].type}${newops[i].name && newops[i].name !== '' ? '-' + newops[i].name : ''}`
|
||||||
),
|
),
|
||||||
String(newops[i].value)
|
String(newops[i].value)
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newops
|
return newops;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the headers from an array of FetchAPIOperation objects and returns them as an object.
|
* Retrieves the headers from an array of FetchAPIOperation objects and returns them as an object.
|
||||||
@@ -183,77 +177,75 @@ const GoAPIHeaders = (
|
|||||||
ops: Array<FetchAPIOperation>,
|
ops: Array<FetchAPIOperation>,
|
||||||
headers?: Headers
|
headers?: Headers
|
||||||
): { [key: string]: string } => {
|
): { [key: string]: string } => {
|
||||||
const head = new Headers()
|
const head = new Headers();
|
||||||
const headerlist: Record<string,string> = {}
|
const headerlist: Record<string, string> = {};
|
||||||
|
|
||||||
const authToken = getAuthToken?.()
|
const authToken = getAuthToken?.();
|
||||||
if (authToken && authToken !== '') {
|
if (authToken && authToken !== '') {
|
||||||
|
head.set('Authorization', `Token ${authToken}`);
|
||||||
head.set('Authorization', `Token ${authToken}`)
|
|
||||||
} else {
|
} else {
|
||||||
const token = getAuthToken()
|
const token = getAuthToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
head.set('Authorization', `Token ${token}`)
|
head.set('Authorization', `Token ${token}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (headers) {
|
if (headers) {
|
||||||
headers.forEach((v, k) => {
|
headers.forEach((v, k) => {
|
||||||
head.set(k, v)
|
head.set(k, v);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
const distinctOperations: Array<FetchAPIOperation> = []
|
const distinctOperations: Array<FetchAPIOperation> = [];
|
||||||
|
|
||||||
for (const value of ops?.filter((val) => !!val) ?? []) {
|
for (const value of ops?.filter((val) => !!val) ?? []) {
|
||||||
const index = distinctOperations.findIndex(
|
const index = distinctOperations.findIndex(
|
||||||
(searchValue) => searchValue.name === value.name && searchValue.type === value.type
|
(searchValue) => searchValue.name === value.name && searchValue.type === value.type
|
||||||
)
|
);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
distinctOperations.push(value)
|
distinctOperations.push(value);
|
||||||
} else {
|
} else {
|
||||||
distinctOperations[index] = value
|
distinctOperations[index] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildGoAPIOperation(distinctOperations, head)
|
buildGoAPIOperation(distinctOperations, head);
|
||||||
|
|
||||||
head?.forEach((v, k) => {
|
head?.forEach((v, k) => {
|
||||||
headerlist[k] = v
|
headerlist[k] = v;
|
||||||
})
|
});
|
||||||
|
|
||||||
if (headers) {
|
if (headers) {
|
||||||
for (const key of Object.keys(headerlist)) {
|
for (const key of Object.keys(headerlist)) {
|
||||||
headers.set(key, headerlist[key])
|
headers.set(key, headerlist[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return headerlist
|
return headerlist;
|
||||||
}
|
};
|
||||||
|
|
||||||
const callbacks = {
|
const callbacks = {
|
||||||
getAuthToken: () => {
|
getAuthToken: () => {
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
const token = localStorage.getItem(TOKEN_KEY)
|
const token = localStorage.getItem(TOKEN_KEY);
|
||||||
if (token) {
|
if (token) {
|
||||||
return token
|
return token;
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the authentication token from local storage.
|
* Retrieves the authentication token from local storage.
|
||||||
*
|
*
|
||||||
* @return {string | undefined} The authentication token if found, otherwise undefined
|
* @return {string | undefined} The authentication token if found, otherwise undefined
|
||||||
*/
|
*/
|
||||||
const getAuthToken = () => callbacks?.getAuthToken?.()
|
const getAuthToken = () => callbacks?.getAuthToken?.();
|
||||||
|
|
||||||
const setAuthTokenCallback = (cb: () => string) => {
|
const setAuthTokenCallback = (cb: () => string) => {
|
||||||
callbacks.getAuthToken = cb
|
callbacks.getAuthToken = cb;
|
||||||
return callbacks.getAuthToken
|
return callbacks.getAuthToken;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the authentication token in the local storage.
|
* Sets the authentication token in the local storage.
|
||||||
@@ -262,9 +254,8 @@ const setAuthTokenCallback = (cb: ()=> string) => {
|
|||||||
*/
|
*/
|
||||||
const setAuthToken = (token: string) => {
|
const setAuthToken = (token: string) => {
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
localStorage.setItem(TOKEN_KEY, token)
|
localStorage.setItem(TOKEN_KEY, token);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { buildGoAPIOperation, getAuthToken, GoAPIHeaders, setAuthToken, setAuthTokenCallback };
|
||||||
export {buildGoAPIOperation,getAuthToken,GoAPIHeaders,setAuthToken,setAuthTokenCallback}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user