@efa-one/sdk 1.10.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.
Files changed (43) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +97 -0
  4. package/backend/apiRegistry.d.ts +22 -0
  5. package/backend/apiRegistry.js +62 -0
  6. package/backend/audit.d.ts +1 -0
  7. package/backend/audit.js +54 -0
  8. package/backend/auth.d.ts +31 -0
  9. package/backend/auth.js +233 -0
  10. package/backend/customPermissions.d.ts +11 -0
  11. package/backend/customPermissions.js +23 -0
  12. package/backend/health.d.ts +3 -0
  13. package/backend/health.js +23 -0
  14. package/backend/i18n-backend.d.ts +2 -0
  15. package/backend/i18n-backend.js +21 -0
  16. package/backend/index.d.ts +14 -0
  17. package/backend/index.js +30 -0
  18. package/backend/mail.d.ts +18 -0
  19. package/backend/mail.js +43 -0
  20. package/backend/notifications.d.ts +9 -0
  21. package/backend/notifications.js +24 -0
  22. package/backend/permissionCheck.d.ts +1 -0
  23. package/backend/permissionCheck.js +12 -0
  24. package/backend/permissionClient.d.ts +1 -0
  25. package/backend/permissionClient.js +11 -0
  26. package/backend/permissions.d.ts +11 -0
  27. package/backend/permissions.js +66 -0
  28. package/backend/reporting.d.ts +6 -0
  29. package/backend/reporting.js +31 -0
  30. package/backend/serviceClient.d.ts +17 -0
  31. package/backend/serviceClient.js +77 -0
  32. package/backend/serviceDiscovery.d.ts +28 -0
  33. package/backend/serviceDiscovery.js +57 -0
  34. package/frontend/DevHeader.d.ts +8 -0
  35. package/frontend/DevHeader.js +42 -0
  36. package/frontend/i18n.d.ts +3 -0
  37. package/frontend/i18n.js +16 -0
  38. package/frontend/index.d.ts +3 -0
  39. package/frontend/index.js +3 -0
  40. package/frontend/ipc.d.ts +11 -0
  41. package/frontend/ipc.js +39 -0
  42. package/frontend/package.json +1 -0
  43. package/package.json +96 -0
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serviceClient = exports.ServiceClient = void 0;
4
+ function log(level, msg, extra) {
5
+ console.log(JSON.stringify({ level, msg, timestamp: new Date().toISOString(), ...extra }));
6
+ }
7
+ class ServiceClient {
8
+ constructor(options) {
9
+ this.gatewayUrl = (options?.gatewayUrl ?? process.env.CONVERGE_GATEWAY_URL ?? '').replace(/\/$/, '');
10
+ this.registryKey = options?.registryKey ?? process.env.SERVICE_AUTH_KEY ?? '';
11
+ this.fetchImpl = options?.fetchImpl ?? fetch;
12
+ this.retries = options?.retries ?? 2;
13
+ this.retryDelay = options?.retryDelayMs ?? 1000;
14
+ }
15
+ async call(serviceKey, method, path, body) {
16
+ if (!this.gatewayUrl) {
17
+ throw new Error('ServiceClient: CONVERGE_GATEWAY_URL nicht gesetzt');
18
+ }
19
+ const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
20
+ const url = `${this.gatewayUrl}/api/gateway/${encodeURIComponent(serviceKey)}/${normalizedPath}`;
21
+ const headers = {
22
+ 'Content-Type': 'application/json',
23
+ 'Accept': 'application/json',
24
+ };
25
+ if (this.registryKey) {
26
+ headers['X-Service-Auth-Key'] = this.registryKey;
27
+ }
28
+ const fetchOptions = { method, headers };
29
+ if (body !== undefined && method !== 'GET' && method !== 'HEAD') {
30
+ fetchOptions.body = JSON.stringify(body);
31
+ }
32
+ for (let attempt = 1; attempt <= this.retries + 1; attempt++) {
33
+ try {
34
+ const res = await this.fetchImpl(url, fetchOptions);
35
+ if (res.status === 204) {
36
+ return undefined;
37
+ }
38
+ if (!res.ok) {
39
+ let errorMessage = `ServiceClient: ${serviceKey} ${method} ${path} → ${res.status}`;
40
+ try {
41
+ const errBody = await res.json();
42
+ if (errBody.error)
43
+ errorMessage = errBody.error;
44
+ }
45
+ catch { }
46
+ if (res.status >= 400 && res.status < 500) {
47
+ throw new Error(errorMessage);
48
+ }
49
+ if (attempt <= this.retries) {
50
+ log('warn', 'ServiceClient: Retry nach Server-Fehler', {
51
+ serviceKey, method, path, status: res.status, attempt,
52
+ });
53
+ await new Promise((r) => setTimeout(r, this.retryDelay));
54
+ continue;
55
+ }
56
+ throw new Error(errorMessage);
57
+ }
58
+ return res.json();
59
+ }
60
+ catch (err) {
61
+ if (err instanceof Error && !err.message.startsWith('ServiceClient:')) {
62
+ if (attempt <= this.retries) {
63
+ log('warn', 'ServiceClient: Netzwerkfehler, Retry', {
64
+ serviceKey, method, path, err: String(err), attempt,
65
+ });
66
+ await new Promise((r) => setTimeout(r, this.retryDelay));
67
+ continue;
68
+ }
69
+ }
70
+ throw err;
71
+ }
72
+ }
73
+ throw new Error(`ServiceClient: Alle Versuche fehlgeschlagen für ${serviceKey} ${method} ${path}`);
74
+ }
75
+ }
76
+ exports.ServiceClient = ServiceClient;
77
+ exports.serviceClient = new ServiceClient();
@@ -0,0 +1,28 @@
1
+ export type RegistryStatus = 'running' | 'stopped' | 'not_found' | 'error';
2
+ export interface ResolveServiceResponse {
3
+ serviceKey: string;
4
+ baseUrlInternal: string;
5
+ healthUrl: string;
6
+ openApiUrl: string | null;
7
+ status: RegistryStatus;
8
+ }
9
+ export interface RegistryServiceDetailResponse extends ResolveServiceResponse {
10
+ capabilities: Array<{
11
+ id: string;
12
+ serviceId: string;
13
+ capabilityKey: string;
14
+ endpointPath: string;
15
+ method: string;
16
+ version: string | null;
17
+ }>;
18
+ }
19
+ export declare function clearServiceDiscoveryCache(): void;
20
+ export declare function resolveService(serviceKey: string, options?: {
21
+ registryBaseUrl?: string;
22
+ ttlMs?: number;
23
+ fetchImpl?: typeof fetch;
24
+ }): Promise<ResolveServiceResponse>;
25
+ export declare function resolveServiceDetail(serviceKey: string, options?: {
26
+ registryBaseUrl?: string;
27
+ fetchImpl?: typeof fetch;
28
+ }): Promise<RegistryServiceDetailResponse>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearServiceDiscoveryCache = clearServiceDiscoveryCache;
4
+ exports.resolveService = resolveService;
5
+ exports.resolveServiceDetail = resolveServiceDetail;
6
+ const cache = new Map();
7
+ function normalizeRegistryBaseUrl(registryBaseUrl) {
8
+ return registryBaseUrl.replace(/\/$/, '');
9
+ }
10
+ function clearServiceDiscoveryCache() {
11
+ cache.clear();
12
+ }
13
+ async function resolveService(serviceKey, options) {
14
+ const ttlMs = options?.ttlMs ?? 60000;
15
+ const now = Date.now();
16
+ const cached = cache.get(serviceKey);
17
+ if (cached && cached.expiresAt > now)
18
+ return cached.value;
19
+ const fetchImpl = options?.fetchImpl ?? fetch;
20
+ const baseUrl = normalizeRegistryBaseUrl(options?.registryBaseUrl ?? process.env.CONVERGE_REGISTRY_URL ?? '');
21
+ if (!baseUrl) {
22
+ throw new Error('CONVERGE_REGISTRY_URL ist nicht gesetzt');
23
+ }
24
+ const headers = { Accept: 'application/json' };
25
+ const registryKey = process.env.SERVICE_AUTH_KEY;
26
+ if (registryKey)
27
+ headers['X-Service-Auth-Key'] = registryKey;
28
+ const response = await fetchImpl(`${baseUrl}/api/registry/resolve/${encodeURIComponent(serviceKey)}`, {
29
+ method: 'GET',
30
+ headers,
31
+ });
32
+ if (!response.ok) {
33
+ throw new Error(`Service-Aufloesung fehlgeschlagen (${response.status})`);
34
+ }
35
+ const resolved = await response.json();
36
+ cache.set(serviceKey, { value: resolved, expiresAt: now + ttlMs });
37
+ return resolved;
38
+ }
39
+ async function resolveServiceDetail(serviceKey, options) {
40
+ const fetchImpl = options?.fetchImpl ?? fetch;
41
+ const baseUrl = normalizeRegistryBaseUrl(options?.registryBaseUrl ?? process.env.CONVERGE_REGISTRY_URL ?? '');
42
+ if (!baseUrl) {
43
+ throw new Error('CONVERGE_REGISTRY_URL ist nicht gesetzt');
44
+ }
45
+ const detailHeaders = { Accept: 'application/json' };
46
+ const detailRegistryKey = process.env.SERVICE_AUTH_KEY;
47
+ if (detailRegistryKey)
48
+ detailHeaders['X-Service-Auth-Key'] = detailRegistryKey;
49
+ const response = await fetchImpl(`${baseUrl}/api/registry/services/${encodeURIComponent(serviceKey)}`, {
50
+ method: 'GET',
51
+ headers: detailHeaders,
52
+ });
53
+ if (!response.ok) {
54
+ throw new Error(`Service-Details konnten nicht geladen werden (${response.status})`);
55
+ }
56
+ return response.json();
57
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface DevHeaderProps {
3
+ appName: string;
4
+ onBack: () => void;
5
+ onSettings: () => void;
6
+ }
7
+ export default function DevHeader({ appName, onBack, onSettings }: DevHeaderProps): React.JSX.Element | null;
8
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export default function DevHeader({ appName, onBack, onSettings }) {
3
+ if (window.self !== window.top)
4
+ return null;
5
+ return (_jsxs("header", { style: {
6
+ display: 'flex',
7
+ alignItems: 'center',
8
+ gap: '12px',
9
+ padding: '0 16px',
10
+ height: '52px',
11
+ background: '#1e293b',
12
+ color: '#f1f5f9',
13
+ flexShrink: 0,
14
+ fontFamily: 'system-ui, sans-serif',
15
+ fontSize: '14px',
16
+ }, children: [_jsx("span", { style: {
17
+ background: '#f59e0b',
18
+ color: '#1c1917',
19
+ fontSize: '11px',
20
+ fontWeight: 700,
21
+ padding: '2px 8px',
22
+ borderRadius: '4px',
23
+ letterSpacing: '0.05em',
24
+ flexShrink: 0,
25
+ }, children: "DEV MODE" }), _jsx("button", { onClick: onBack, title: "Back to dashboard", style: {
26
+ background: 'transparent',
27
+ border: '1px solid #475569',
28
+ color: '#94a3b8',
29
+ borderRadius: '6px',
30
+ padding: '4px 10px',
31
+ cursor: 'pointer',
32
+ fontSize: '13px',
33
+ }, children: "\u2190 Back" }), _jsx("span", { style: { fontWeight: 600, color: '#f1f5f9', flex: 1 }, children: appName }), _jsx("button", { onClick: onSettings, title: "Settings", style: {
34
+ background: 'transparent',
35
+ border: 'none',
36
+ color: '#94a3b8',
37
+ cursor: 'pointer',
38
+ fontSize: '18px',
39
+ padding: '4px 8px',
40
+ borderRadius: '6px',
41
+ }, children: "\u2699" })] }));
42
+ }
@@ -0,0 +1,3 @@
1
+ import i18n from 'i18next';
2
+ export declare function initI18n(defaultLanguage?: string): typeof i18n;
3
+ export { i18n };
@@ -0,0 +1,16 @@
1
+ import i18n from 'i18next';
2
+ import { initReactI18next } from 'react-i18next';
3
+ import HttpBackend from 'i18next-http-backend';
4
+ export function initI18n(defaultLanguage = 'de') {
5
+ i18n
6
+ .use(HttpBackend)
7
+ .use(initReactI18next)
8
+ .init({
9
+ lng: defaultLanguage,
10
+ fallbackLng: 'de',
11
+ interpolation: { escapeValue: false },
12
+ backend: { loadPath: '/locales/{{lng}}/translation.json' },
13
+ });
14
+ return i18n;
15
+ }
16
+ export { i18n };
@@ -0,0 +1,3 @@
1
+ export * from './ipc';
2
+ export * from './i18n';
3
+ export { default as DevHeader } from './DevHeader';
@@ -0,0 +1,3 @@
1
+ export * from './ipc';
2
+ export * from './i18n';
3
+ export { default as DevHeader } from './DevHeader';
@@ -0,0 +1,11 @@
1
+ export declare function getParentOrigin(): string;
2
+ export declare function isFromPlatformParent(event: MessageEvent): boolean;
3
+ export declare function sendAtStart(): void;
4
+ export declare function sendDeclareAppInfo(payload: {
5
+ appName: string;
6
+ version: string;
7
+ }): void;
8
+ export declare function navigateToApp(serviceKey: string, options?: {
9
+ path?: string;
10
+ }): void;
11
+ export declare function notifyRouteChange(path: string): void;
@@ -0,0 +1,39 @@
1
+ function isEmbedded() {
2
+ try {
3
+ return window.parent !== window;
4
+ }
5
+ catch {
6
+ return false;
7
+ }
8
+ }
9
+ export function getParentOrigin() {
10
+ try {
11
+ return window.location.ancestorOrigins?.[0] ?? window.location.origin;
12
+ }
13
+ catch {
14
+ return window.location.origin;
15
+ }
16
+ }
17
+ export function isFromPlatformParent(event) {
18
+ return event.source === window.parent;
19
+ }
20
+ export function sendAtStart() {
21
+ if (!isEmbedded())
22
+ return;
23
+ window.parent.postMessage({ type: 'CONVERGE_AT_START' }, getParentOrigin());
24
+ }
25
+ export function sendDeclareAppInfo(payload) {
26
+ if (!isEmbedded())
27
+ return;
28
+ window.parent.postMessage({ type: 'CONVERGE_DECLARE_APP_INFO', payload }, getParentOrigin());
29
+ }
30
+ export function navigateToApp(serviceKey, options) {
31
+ if (!isEmbedded())
32
+ return;
33
+ window.parent.postMessage({ type: 'CONVERGE_NAVIGATE_TO_APP', payload: { serviceKey, path: options?.path } }, getParentOrigin());
34
+ }
35
+ export function notifyRouteChange(path) {
36
+ if (!isEmbedded())
37
+ return;
38
+ window.parent.postMessage({ type: 'CONVERGE_ROUTE_CHANGED', path }, getParentOrigin());
39
+ }
@@ -0,0 +1 @@
1
+ {"type":"module"}
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@efa-one/sdk",
3
+ "version": "1.10.1",
4
+ "description": "Offizielles SDK für efa-one-Apps: Auth/Session, Service-Discovery, Gateway-Client, Audit/Reporting/Mail/Notifications, Permission- und Capability-Registrierung sowie das Frontend-IPC-/i18n-Toolkit.",
5
+ "license": "Apache-2.0",
6
+ "type": "commonjs",
7
+ "exports": {
8
+ "./backend": {
9
+ "types": "./backend/index.d.ts",
10
+ "require": "./backend/index.js",
11
+ "default": "./backend/index.js"
12
+ },
13
+ "./backend/*": {
14
+ "types": "./backend/*.d.ts",
15
+ "require": "./backend/*.js",
16
+ "default": "./backend/*.js"
17
+ },
18
+ "./frontend": {
19
+ "types": "./frontend/index.d.ts",
20
+ "import": "./frontend/index.js",
21
+ "default": "./frontend/index.js"
22
+ },
23
+ "./frontend/*": {
24
+ "types": "./frontend/*.d.ts",
25
+ "import": "./frontend/*.js",
26
+ "default": "./frontend/*.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "backend",
32
+ "frontend",
33
+ "NOTICE"
34
+ ],
35
+ "scripts": {
36
+ "clean": "rm -rf backend frontend",
37
+ "build:backend": "tsc -p tsconfig.backend.json",
38
+ "build:frontend": "tsc -p tsconfig.frontend.json && node -e \"require('fs').writeFileSync('frontend/package.json', JSON.stringify({type:'module'}))\"",
39
+ "build": "npm run clean && npm run build:backend && npm run build:frontend",
40
+ "typecheck": "tsc -p tsconfig.backend.json --noEmit && tsc -p tsconfig.frontend.json --noEmit",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "peerDependencies": {
47
+ "express": ">=4",
48
+ "i18next": ">=23",
49
+ "i18next-http-backend": ">=2",
50
+ "jsonwebtoken": ">=9",
51
+ "pg": ">=8",
52
+ "react": ">=18",
53
+ "react-i18next": ">=13"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "express": {
57
+ "optional": true
58
+ },
59
+ "pg": {
60
+ "optional": true
61
+ },
62
+ "jsonwebtoken": {
63
+ "optional": true
64
+ },
65
+ "react": {
66
+ "optional": true
67
+ },
68
+ "i18next": {
69
+ "optional": true
70
+ },
71
+ "i18next-http-backend": {
72
+ "optional": true
73
+ },
74
+ "react-i18next": {
75
+ "optional": true
76
+ }
77
+ },
78
+ "devDependencies": {
79
+ "@types/express": "^4.17.21",
80
+ "@types/jsonwebtoken": "^9.0.6",
81
+ "@types/node": "^20.14.0",
82
+ "@types/pg": "^8.11.0",
83
+ "@types/react": "^18.2.46",
84
+ "express": "^4.19.2",
85
+ "i18next": "^23.11.0",
86
+ "i18next-http-backend": "^2.7.3",
87
+ "jsonwebtoken": "^9.0.2",
88
+ "pg": "^8.12.0",
89
+ "react": "^18.2.0",
90
+ "react-i18next": "^13.5.0",
91
+ "typescript": "7.0.2"
92
+ },
93
+ "engines": {
94
+ "node": ">=20"
95
+ }
96
+ }