@nsxbet/admin-sdk 0.7.1 → 0.8.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 (65) hide show
  1. package/CHECKLIST.md +48 -13
  2. package/README.md +24 -74
  3. package/dist/auth/client/bff.d.ts +38 -0
  4. package/dist/auth/client/bff.js +270 -0
  5. package/dist/auth/client/in-memory.d.ts +1 -1
  6. package/dist/auth/client/in-memory.js +2 -2
  7. package/dist/auth/client/index.d.ts +1 -1
  8. package/dist/auth/client/index.js +2 -2
  9. package/dist/auth/client/interface.d.ts +4 -4
  10. package/dist/auth/client/interface.js +1 -1
  11. package/dist/auth/components/LoginPage.d.ts +8 -0
  12. package/dist/auth/components/LoginPage.js +32 -0
  13. package/dist/auth/components/UserSelector.js +2 -2
  14. package/dist/auth/components/index.d.ts +2 -0
  15. package/dist/auth/components/index.js +1 -0
  16. package/dist/auth/index.d.ts +3 -2
  17. package/dist/auth/index.js +2 -2
  18. package/dist/components/AuthProvider.d.ts +3 -3
  19. package/dist/components/AuthProvider.js +25 -10
  20. package/dist/env.d.ts +17 -0
  21. package/dist/env.js +50 -0
  22. package/dist/hooks/useAuth.d.ts +3 -3
  23. package/dist/hooks/useAuth.js +1 -1
  24. package/dist/hooks/useFetch.js +6 -1
  25. package/dist/hooks/useI18n.js +2 -2
  26. package/dist/i18n/config.d.ts +2 -1
  27. package/dist/i18n/config.js +4 -3
  28. package/dist/i18n/index.d.ts +1 -1
  29. package/dist/i18n/index.js +1 -1
  30. package/dist/i18n/locales/en-US.json +7 -0
  31. package/dist/i18n/locales/es.json +7 -0
  32. package/dist/i18n/locales/pt-BR.json +7 -0
  33. package/dist/i18n/locales/ro.json +7 -0
  34. package/dist/index.d.ts +6 -5
  35. package/dist/index.js +5 -2
  36. package/dist/registry/client/http.js +6 -1
  37. package/dist/registry/client/in-memory.js +20 -5
  38. package/dist/registry/types/manifest.d.ts +5 -0
  39. package/dist/registry/types/manifest.js +4 -1
  40. package/dist/registry/types/module.d.ts +6 -2
  41. package/dist/sdk-version.d.ts +5 -0
  42. package/dist/sdk-version.js +5 -0
  43. package/dist/shell/AdminShell.d.ts +12 -9
  44. package/dist/shell/AdminShell.js +56 -70
  45. package/dist/shell/components/ModuleOverview.js +1 -5
  46. package/dist/shell/components/RegistryPage.js +1 -1
  47. package/dist/shell/components/TopBar.js +2 -2
  48. package/dist/shell/index.d.ts +1 -1
  49. package/dist/shell/polling-config.d.ts +4 -3
  50. package/dist/shell/polling-config.js +11 -9
  51. package/dist/shell/types.d.ts +3 -1
  52. package/dist/types/platform.d.ts +2 -11
  53. package/dist/vite/config.d.ts +4 -9
  54. package/dist/vite/config.js +85 -27
  55. package/dist/vite/index.d.ts +1 -1
  56. package/dist/vite/index.js +1 -1
  57. package/dist/vite/plugins.js +6 -1
  58. package/package.json +11 -5
  59. package/scripts/write-sdk-version.mjs +21 -0
  60. package/dist/auth/client/keycloak.d.ts +0 -18
  61. package/dist/auth/client/keycloak.js +0 -129
  62. package/dist/shell/BackofficeShell.d.ts +0 -37
  63. package/dist/shell/BackofficeShell.js +0 -339
  64. package/dist/types/keycloak.d.ts +0 -25
  65. package/dist/types/keycloak.js +0 -1
@@ -2,18 +2,96 @@ import { loadEnv } from "vite";
2
2
  import { generateModuleManifestPlugin, serveDistPlugin } from "./plugins.js";
3
3
  import { adminModuleI18nPlugin } from "./i18n-plugin.js";
4
4
  import { getSharedExternals } from "./AdminShellSharedDeps.js";
5
- const ENV_KEY = "VITE_ADMIN_GATEWAY_URL";
6
- /**
7
- * Default admin gateway URL (staging environment).
8
- * Used by the env injection plugin when no explicit value is configured.
9
- */
10
- export const ADMIN_GATEWAY_STAGING_URL = "https://admin-bff-stg.nsx.dev";
5
+ /** Default gateway when no env or option is set (internal to the plugin). */
6
+ const DEFAULT_GATEWAY_URL = "https://admin-bff-stg.nsx.dev";
11
7
  /**
12
8
  * Shared dependencies that are externalized in module builds.
13
9
  * These are provided by the shell via import maps.
14
10
  * Derived from SHARED_DEPS_CONFIG — the single source of truth.
15
11
  */
16
12
  export const SHARED_EXTERNALS = getSharedExternals();
13
+ function buildEnvJs(mode, gatewayUrl) {
14
+ const loaded = loadEnv(mode, process.cwd(), "");
15
+ const fromEnv = process.env.ADMIN_GATEWAY_URL || loaded.ADMIN_GATEWAY_URL;
16
+ let adminGateway;
17
+ if (fromEnv) {
18
+ adminGateway = fromEnv;
19
+ }
20
+ else if (gatewayUrl !== undefined && gatewayUrl !== null) {
21
+ adminGateway = gatewayUrl;
22
+ }
23
+ else {
24
+ adminGateway = DEFAULT_GATEWAY_URL;
25
+ }
26
+ const entries = {
27
+ ADMIN_GATEWAY_URL: adminGateway,
28
+ };
29
+ const mockAuth = loaded.MOCK_AUTH ?? process.env.MOCK_AUTH;
30
+ if (mockAuth !== undefined && mockAuth !== "") {
31
+ entries.MOCK_AUTH = mockAuth;
32
+ }
33
+ const environment = loaded.ENVIRONMENT ?? process.env.ENVIRONMENT;
34
+ if (environment !== undefined && environment !== "") {
35
+ entries.ENVIRONMENT = environment;
36
+ }
37
+ const registryPollInterval = loaded.REGISTRY_POLL_INTERVAL ?? process.env.REGISTRY_POLL_INTERVAL;
38
+ if (registryPollInterval !== undefined && registryPollInterval !== "") {
39
+ entries.REGISTRY_POLL_INTERVAL = registryPollInterval;
40
+ }
41
+ const body = Object.entries(entries)
42
+ .map(([k, v]) => `${k}:${JSON.stringify(v)}`)
43
+ .join(",");
44
+ return `window.__ENV__={${body}};`;
45
+ }
46
+ function createAdminModuleEnvPlugin(gatewayUrl) {
47
+ return {
48
+ name: "admin-module-env",
49
+ configureServer(server) {
50
+ if (gatewayUrl === null)
51
+ return;
52
+ server.middlewares.use((req, res, next) => {
53
+ const url = req.url?.split("?")[0];
54
+ if (url === "/env.js") {
55
+ const mode = server.config.mode;
56
+ res.setHeader("Content-Type", "application/javascript");
57
+ res.setHeader("Cache-Control", "no-cache");
58
+ res.end(buildEnvJs(mode, gatewayUrl));
59
+ return;
60
+ }
61
+ next();
62
+ });
63
+ },
64
+ configurePreviewServer(server) {
65
+ if (gatewayUrl === null)
66
+ return;
67
+ server.middlewares.use((req, res, next) => {
68
+ const url = req.url?.split("?")[0];
69
+ if (url === "/env.js") {
70
+ const mode = server.config.mode;
71
+ res.setHeader("Content-Type", "application/javascript");
72
+ res.setHeader("Cache-Control", "no-cache");
73
+ res.end(buildEnvJs(mode, gatewayUrl));
74
+ return;
75
+ }
76
+ next();
77
+ });
78
+ },
79
+ transformIndexHtml: {
80
+ order: "pre",
81
+ handler(html) {
82
+ if (gatewayUrl === null)
83
+ return html;
84
+ if (html.includes('src="/env.js"') || html.includes("src='/env.js'")) {
85
+ return html;
86
+ }
87
+ if (html.includes("</head>")) {
88
+ return html.replace("</head>", ` <script src="/env.js"></script>\n </head>`);
89
+ }
90
+ return html;
91
+ },
92
+ },
93
+ };
94
+ }
17
95
  /**
18
96
  * Composable Vite plugin array for admin modules.
19
97
  * Returns plugins that handle build-time lib config, dist serving, and manifest generation.
@@ -54,26 +132,6 @@ export function adminModule(options = {}) {
54
132
  return true;
55
133
  return process.env.ADMIN_MODULE === "true";
56
134
  }
57
- const envPlugin = {
58
- name: "admin-module-env",
59
- config(_config, { mode }) {
60
- if (gatewayUrl === null)
61
- return;
62
- const env = loadEnv(mode, process.cwd(), "VITE_");
63
- const fromEnv = process.env[ENV_KEY] || env[ENV_KEY];
64
- if (fromEnv) {
65
- console.log(`[admin-module-env] ${ENV_KEY} → ${fromEnv} (from env)`);
66
- return;
67
- }
68
- const url = gatewayUrl ?? ADMIN_GATEWAY_STAGING_URL;
69
- console.log(`[admin-module-env] ${ENV_KEY} → ${url} (auto-injected)`);
70
- return {
71
- define: {
72
- [`import.meta.env.${ENV_KEY}`]: JSON.stringify(url),
73
- },
74
- };
75
- },
76
- };
77
135
  const buildConfigPlugin = {
78
136
  name: "admin-module-build-config",
79
137
  config(_config, { command }) {
@@ -100,7 +158,7 @@ export function adminModule(options = {}) {
100
158
  },
101
159
  };
102
160
  return [
103
- envPlugin,
161
+ createAdminModuleEnvPlugin(gatewayUrl),
104
162
  adminModuleI18nPlugin(),
105
163
  serveDistPlugin({ distDir: outDir }),
106
164
  generateModuleManifestPlugin(),
@@ -15,5 +15,5 @@
15
15
  */
16
16
  export { generateModuleManifestPlugin, serveDistPlugin, type GenerateModuleManifestPluginOptions, type ServeDistPluginOptions, } from "./plugins.js";
17
17
  export { adminModuleI18nPlugin, type AdminModuleI18nPluginOptions, } from "./i18n-plugin.js";
18
- export { adminModule, defineModuleConfig, SHARED_EXTERNALS, ADMIN_GATEWAY_STAGING_URL, type AdminModuleOptions, type ModuleConfigOptions, } from "./config.js";
18
+ export { adminModule, defineModuleConfig, SHARED_EXTERNALS, type AdminModuleOptions, type ModuleConfigOptions, } from "./config.js";
19
19
  export { AdminShellSharedDeps, SHARED_DEPS_CONFIG, getSharedExternals, type SharedDepConfig, } from "./AdminShellSharedDeps.js";
@@ -15,5 +15,5 @@
15
15
  */
16
16
  export { generateModuleManifestPlugin, serveDistPlugin, } from "./plugins.js";
17
17
  export { adminModuleI18nPlugin, } from "./i18n-plugin.js";
18
- export { adminModule, defineModuleConfig, SHARED_EXTERNALS, ADMIN_GATEWAY_STAGING_URL, } from "./config.js";
18
+ export { adminModule, defineModuleConfig, SHARED_EXTERNALS, } from "./config.js";
19
19
  export { AdminShellSharedDeps, SHARED_DEPS_CONFIG, getSharedExternals, } from "./AdminShellSharedDeps.js";
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, writeFileSync, existsSync } from "fs";
2
2
  import { resolve, join } from "path";
3
+ import { SDK_PACKAGE_VERSION } from "../sdk-version.js";
3
4
  /**
4
5
  * Vite plugin to generate module.manifest.json after build.
5
6
  * Merges admin.module.json metadata with the build entry path.
@@ -22,7 +23,11 @@ export function generateModuleManifestPlugin(options = {}) {
22
23
  if (!spaEntry)
23
24
  return;
24
25
  const metadata = JSON.parse(readFileSync(manifestPath, "utf-8"));
25
- const manifest = { entry: spaEntry, ...metadata };
26
+ const manifest = {
27
+ entry: spaEntry,
28
+ sdkVersion: SDK_PACKAGE_VERSION,
29
+ ...metadata,
30
+ };
26
31
  const outDir = outputOptions.dir || "dist";
27
32
  const outputPath = join(outDir, "module.manifest.json");
28
33
  writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nsxbet/admin-sdk",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "SDK for building NSX Admin modules with integrated shell",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,6 +19,12 @@
19
19
  "import": "./dist/vite/index.js",
20
20
  "default": "./dist/vite/index.js"
21
21
  },
22
+ "./env": {
23
+ "source": "./src/env.ts",
24
+ "types": "./dist/env.d.ts",
25
+ "import": "./dist/env.js",
26
+ "default": "./dist/env.js"
27
+ },
22
28
  "./tailwind": {
23
29
  "source": "./src/tailwind/index.ts",
24
30
  "types": "./dist/tailwind/index.d.ts",
@@ -34,9 +40,9 @@
34
40
  ],
35
41
  "scripts": {
36
42
  "postinstall": "node scripts/postinstall.js",
37
- "build": "tsc",
38
- "dev": "tsc --watch --preserveWatchOutput",
39
- "type-check": "tsc --noEmit",
43
+ "build": "node scripts/write-sdk-version.mjs && tsc",
44
+ "dev": "node scripts/write-sdk-version.mjs && tsc --watch --preserveWatchOutput",
45
+ "type-check": "node scripts/write-sdk-version.mjs && tsc --noEmit",
40
46
  "test": "vitest run --passWithNoTests",
41
47
  "storybook": "storybook dev -p 6007 --host 0.0.0.0",
42
48
  "build-storybook": "storybook build -o storybook-static"
@@ -63,7 +69,6 @@
63
69
  }
64
70
  },
65
71
  "dependencies": {
66
- "keycloak-js": "^23.0.0",
67
72
  "lucide-react": "^0.460.0"
68
73
  },
69
74
  "devDependencies": {
@@ -71,6 +76,7 @@
71
76
  "@storybook/react-vite": "^10.3.0",
72
77
  "@testing-library/jest-dom": "^6.9.1",
73
78
  "@testing-library/react": "^16.3.2",
79
+ "@types/node": "^20.3.1",
74
80
  "@types/react": "^18.2.0",
75
81
  "@types/react-dom": "^18.2.0",
76
82
  "@vitejs/plugin-react": "^4.3.4",
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Writes src/sdk-version.ts from package.json version so tsc can use rootDir "./src"
3
+ * without importing files outside src/ (which breaks dist/ layout vs package exports).
4
+ */
5
+ import { readFileSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const pkgPath = join(__dirname, "..", "package.json");
11
+ const outPath = join(__dirname, "..", "src", "sdk-version.ts");
12
+ const { version } = JSON.parse(readFileSync(pkgPath, "utf8"));
13
+
14
+ const contents = `/**
15
+ * Semver of @nsxbet/admin-sdk (synced from package.json by scripts/write-sdk-version.mjs).
16
+ * Do not edit manually — run \`node scripts/write-sdk-version.mjs\` after version bumps.
17
+ */
18
+ export const SDK_PACKAGE_VERSION: string = ${JSON.stringify(version)};
19
+ `;
20
+
21
+ writeFileSync(outPath, contents, "utf8");
@@ -1,18 +0,0 @@
1
- /**
2
- * Keycloak Auth Client
3
- *
4
- * Provides real authentication via Keycloak for production environments.
5
- */
6
- import type { KeycloakConfig } from '../../types/keycloak';
7
- import type { AuthClient } from './interface';
8
- /**
9
- * Options for creating a Keycloak auth client
10
- */
11
- export interface KeycloakAuthClientOptions {
12
- /** Keycloak configuration */
13
- config: KeycloakConfig;
14
- }
15
- /**
16
- * Create a Keycloak auth client for production
17
- */
18
- export declare function createKeycloakAuthClient(options: KeycloakAuthClientOptions): AuthClient;
@@ -1,129 +0,0 @@
1
- /**
2
- * Keycloak Auth Client
3
- *
4
- * Provides real authentication via Keycloak for production environments.
5
- */
6
- import Keycloak from 'keycloak-js';
7
- /**
8
- * Create a Keycloak auth client for production
9
- */
10
- export function createKeycloakAuthClient(options) {
11
- const { config } = options;
12
- let keycloak = null;
13
- let currentUser = null;
14
- let isInitialized = false;
15
- const subscribers = new Set();
16
- /**
17
- * Parse user from Keycloak token
18
- */
19
- function parseUser(tokenParsed) {
20
- return {
21
- id: tokenParsed.sub || '',
22
- email: tokenParsed.email || '',
23
- displayName: tokenParsed.name || tokenParsed.preferred_username || '',
24
- roles: tokenParsed.realm_access?.roles ?? [],
25
- };
26
- }
27
- /**
28
- * Notify subscribers of state change
29
- */
30
- function notifySubscribers() {
31
- const state = {
32
- isAuthenticated: currentUser !== null,
33
- user: currentUser,
34
- };
35
- subscribers.forEach((callback) => callback(state));
36
- }
37
- const client = {
38
- type: 'keycloak',
39
- async initialize() {
40
- if (keycloak) {
41
- return keycloak.authenticated ?? false;
42
- }
43
- keycloak = new Keycloak({
44
- url: config.url || 'http://localhost:8080',
45
- realm: config.realm || 'admin',
46
- clientId: config.clientId,
47
- });
48
- try {
49
- const authenticated = await keycloak.init({
50
- onLoad: 'login-required',
51
- checkLoginIframe: false,
52
- pkceMethod: 'S256',
53
- });
54
- if (authenticated && keycloak.tokenParsed) {
55
- currentUser = parseUser(keycloak.tokenParsed);
56
- }
57
- isInitialized = true;
58
- // Notify subscribers that auth is ready - this triggers React re-render
59
- // which will re-evaluate permissions with the now-available token
60
- notifySubscribers();
61
- // Setup token refresh
62
- keycloak.onTokenExpired = () => {
63
- keycloak?.updateToken(30).catch(() => {
64
- console.error('[AuthClient] Failed to refresh token');
65
- client.logout();
66
- });
67
- };
68
- // Setup auth state changes
69
- keycloak.onAuthLogout = () => {
70
- currentUser = null;
71
- notifySubscribers();
72
- };
73
- keycloak.onAuthRefreshError = () => {
74
- currentUser = null;
75
- notifySubscribers();
76
- };
77
- return authenticated;
78
- }
79
- catch (error) {
80
- console.error('[AuthClient] Keycloak initialization failed:', error);
81
- throw error;
82
- }
83
- },
84
- isAuthenticated() {
85
- return keycloak?.authenticated ?? false;
86
- },
87
- getUser() {
88
- return currentUser;
89
- },
90
- async getAccessToken() {
91
- if (!keycloak) {
92
- throw new Error('Keycloak not initialized');
93
- }
94
- try {
95
- await keycloak.updateToken(5);
96
- return keycloak.token || '';
97
- }
98
- catch (error) {
99
- console.error('[AuthClient] Failed to refresh token');
100
- client.logout();
101
- throw error;
102
- }
103
- },
104
- hasPermission(permission) {
105
- // Deny-by-default: return false until Keycloak is fully initialized.
106
- // This prevents a window where all actions appear permitted during auth init.
107
- // AuthProvider gates rendering behind isAuthenticated, so modules won't
108
- // render permission-gated UI until auth completes.
109
- if (!isInitialized || !keycloak) {
110
- return false;
111
- }
112
- return keycloak.hasRealmRole(permission);
113
- },
114
- logout() {
115
- currentUser = null;
116
- if (keycloak) {
117
- keycloak.logout();
118
- }
119
- notifySubscribers();
120
- },
121
- subscribe(callback) {
122
- subscribers.add(callback);
123
- return () => {
124
- subscribers.delete(callback);
125
- };
126
- },
127
- };
128
- return client;
129
- }
@@ -1,37 +0,0 @@
1
- import type { AuthClient } from "../auth/client/interface";
2
- import type { AdminModuleManifest, RegistryClient } from "../registry";
3
- export interface AdminShellProps {
4
- /**
5
- * Module manifests to load (used for standalone mode when no registryClient)
6
- * In standalone mode, modules are rendered via children prop
7
- */
8
- modules?: AdminModuleManifest[];
9
- /**
10
- * Content to render for standalone module development.
11
- * Only used in standalone mode (when modules prop is provided)
12
- */
13
- children?: React.ReactNode;
14
- /**
15
- * Keycloak configuration
16
- * If not provided, uses in-memory (mock) authentication
17
- */
18
- keycloak?: {
19
- url: string;
20
- realm: string;
21
- clientId: string;
22
- };
23
- /**
24
- * Auth client to use for authentication.
25
- * If not provided, creates one based on keycloak prop or environment.
26
- */
27
- authClient?: AuthClient;
28
- /** Registry client for fetching modules from API */
29
- registryClient?: RegistryClient;
30
- /** API URL for registry management (e.g., "http://localhost:4000/api") */
31
- apiUrl?: string;
32
- /** Use in-memory registry (default: true, ignored if registryClient is provided) */
33
- inMemoryRegistry?: boolean;
34
- /** Environment (default: "local") */
35
- environment?: string;
36
- }
37
- export declare function AdminShell({ modules: manifests, children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry, environment, }: AdminShellProps): import("react/jsx-runtime").JSX.Element;