max-remotes-helper 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # bo-remotes-helper
2
+
3
+ Helper library for Module Federation remotes in the BO ecosystem.
4
+
5
+ ## Purpose
6
+
7
+ `bo-remotes-helper` provides common functionality for remote modules in the BO system, specifically designed to work with Module Federation without the heavy Node.js dependencies that `bo-library` has.
8
+
9
+ ## Features
10
+
11
+ - 🔐 **Lightweight Authentication Context** - No Node.js polyfills required
12
+ - 🛡️ **Secured Component** - Permission-based conditional rendering
13
+ - 🔧 **Remote Utilities** - Common functions for remote modules
14
+ - 📦 **Module Federation Ready** - Pre-configured shared dependencies
15
+ - 💾 **Token Management** - Browser-compatible token handling
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install bo-remotes-helper
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Basic Setup
26
+
27
+ ```tsx
28
+ import { RemoteWrapper, useRemoteAuth, Secured } from 'bo-remotes-helper';
29
+
30
+ // Wrap your remote component
31
+ export const MyRemoteComponent = ({ token }) => {
32
+ return (
33
+ <RemoteWrapper token={token}>
34
+ <MyContent />
35
+ </RemoteWrapper>
36
+ );
37
+ };
38
+
39
+ // Use authentication context
40
+ const MyContent = () => {
41
+ const { state, hasPermission } = useRemoteAuth();
42
+
43
+ return (
44
+ <div>
45
+ <h1>Welcome {state.user?.name}</h1>
46
+
47
+ <Secured permission="edit">
48
+ <button>Edit Content</button>
49
+ </Secured>
50
+ </div>
51
+ );
52
+ };
53
+ ```
54
+
55
+ ### Module Federation Configuration
56
+
57
+ ```javascript
58
+ import { useRemoteConfig } from 'bo-remotes-helper';
59
+
60
+ const { getWebpackConfig } = useRemoteConfig();
61
+
62
+ const config = getWebpackConfig('my-remote', {
63
+ './MyComponent': './src/MyComponent',
64
+ });
65
+ ```
66
+
67
+ ## API Reference
68
+
69
+ ### Components
70
+
71
+ #### `RemoteWrapper`
72
+ Main wrapper component that provides authentication context.
73
+
74
+ #### `Secured`
75
+ Conditional rendering component based on permissions.
76
+
77
+ ### Hooks
78
+
79
+ #### `useRemoteAuth()`
80
+ Access authentication state and methods.
81
+
82
+ #### `useRemoteConfig()`
83
+ Get standard Module Federation configuration.
84
+
85
+ ### Utils
86
+
87
+ #### Token utilities
88
+ - `decodeToken(token)` - Decode JWT safely
89
+ - `isTokenExpired(token)` - Check token expiration
90
+ - `tokenStorage` - Browser storage utilities
91
+
92
+ #### Module utilities
93
+ - `loadRemoteModule()` - Dynamic remote loading
94
+ - `isRemoteAvailable()` - Check remote availability
95
+
96
+ ## Differences from bo-library
97
+
98
+ | Feature | bo-library | bo-remotes-helper |
99
+ |---------|------------|-------------------|
100
+ | Authentication | Full JWT verification | Client-side decode only |
101
+ | Node.js APIs | Required (crypto, etc.) | Browser-only |
102
+ | Bundle size | Large | Lightweight |
103
+ | Use case | Host applications | Remote modules |
104
+
105
+ ## Development
106
+
107
+ ```bash
108
+ # Install dependencies
109
+ npm install
110
+
111
+ # Build
112
+ npm run build
113
+
114
+ # Watch mode
115
+ npm run dev
116
+
117
+ # Test
118
+ npm run test
119
+ ```
120
+
121
+ ## Integration with bo-module-react-template
122
+
123
+ This library is designed to be the standard authentication solution for all remote modules created from `bo-module-react-template`.
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { AuthProviderProps } from '../types';
3
+ /**
4
+ * Wrapper component that provides authentication context to remote components
5
+ * This is the main component that remote applications should use
6
+ */
7
+ export declare const RemoteWrapper: React.FC<AuthProviderProps>;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { SecuredProps } from '../types';
3
+ /**
4
+ * Secured component for conditional rendering based on permissions
5
+ * Compatible with Module Federation
6
+ */
7
+ export declare const Secured: React.FC<SecuredProps>;
@@ -0,0 +1,9 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { RemoteAuthContextValue } from '../types';
3
+ interface RemoteAuthProviderProps {
4
+ children: ReactNode;
5
+ initialToken?: string;
6
+ }
7
+ export declare const RemoteAuthProvider: React.FC<RemoteAuthProviderProps>;
8
+ export declare const useRemoteAuth: () => RemoteAuthContextValue;
9
+ export {};
@@ -0,0 +1,13 @@
1
+ import { SharedDependencies } from '../types';
2
+ /**
3
+ * Hook that provides standard Module Federation configuration for remotes
4
+ */
5
+ export declare const useRemoteConfig: () => {
6
+ sharedDependencies: SharedDependencies;
7
+ getWebpackConfig: (remoteName: string, exposedComponents: Record<string, string>) => {
8
+ name: string;
9
+ filename: string;
10
+ exposes: Record<string, string>;
11
+ shared: SharedDependencies;
12
+ };
13
+ };
@@ -0,0 +1,7 @@
1
+ export { RemoteAuthProvider, useRemoteAuth } from './context/RemoteAuthContext';
2
+ export { Secured } from './components/Secured';
3
+ export { RemoteWrapper } from './components/RemoteWrapper';
4
+ export { useRemoteConfig } from './hooks/useRemoteConfig';
5
+ export { decodeToken, isTokenExpired, extractPermissions, tokenStorage } from './utils/tokenUtils';
6
+ export { loadRemoteModule, isRemoteAvailable, createRemoteComponentLoader, getRemoteEnvironment } from './utils/moduleUtils';
7
+ export type { RemoteAuthState, RemoteAuthContextValue, AuthProviderProps, SecuredProps, TokenInfo, RemoteConfig, SharedDependencies, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ /*! For license information please see index.js.LICENSE.txt */
2
+ (()=>{var e={20:(e,r,t)=>{"use strict";var o=t(953),n=Symbol.for("react.element"),s=Symbol.for("react.fragment"),a=Object.prototype.hasOwnProperty,i=o.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};r.Fragment=s,r.jsx=function(e,r,t){var o,s={},c=null,d=null;for(o in void 0!==t&&(c=""+t),void 0!==r.key&&(c=""+r.key),void 0!==r.ref&&(d=r.ref),r)a.call(r,o)&&!l.hasOwnProperty(o)&&(s[o]=r[o]);if(e&&e.defaultProps)for(o in r=e.defaultProps)void 0===s[o]&&(s[o]=r[o]);return{$$typeof:n,type:e,key:c,ref:d,props:s,_owner:i.current}}},60:(e,r,t)=>{var o={"./moduleUtils":226,"./moduleUtils.ts":226,"./tokenUtils":433,"./tokenUtils.ts":433};function n(e){return Promise.resolve().then(()=>{if(!t.o(o,e)){var r=new Error("Cannot find module '"+e+"'");throw r.code="MODULE_NOT_FOUND",r}return t(o[e])})}n.keys=()=>Object.keys(o),n.id=60,e.exports=n},226:(e,r,t)=>{"use strict";t.r(r),t.d(r,{createRemoteComponentLoader:()=>i,getRemoteEnvironment:()=>l,isRemoteAvailable:()=>a,loadRemoteModule:()=>s});var o=t(953),n=t.n(o);const s=async(e,r)=>{try{return await t(60)(`${e}/${r}`)}catch(t){throw console.error(`Failed to load remote module ${e}/${r}:`,t),t}},a=async e=>{try{const r=window[e];return"function"==typeof r?.get}catch(e){return!1}},i=(e,r,t)=>n().lazy(async()=>{try{return await s(e,r)}catch(o){return console.error(`Remote component loading failed: ${e}/${r}`,o),t?{default:t}:{default:()=>n().createElement("div",{style:{padding:"20px",color:"red"}},`Failed to load remote component: ${e}/${r}`)}}}),l=()=>({isDevelopment:!1,apiBaseUrl:process.env.REACT_APP_API_URL||"",frontUrl:process.env.REACT_APP_FRONT_URL||""})},433:(e,r,t)=>{"use strict";t.r(r),t.d(r,{decodeToken:()=>o,extractPermissions:()=>s,isTokenExpired:()=>n,tokenStorage:()=>a});const o=e=>{try{if(!e)return null;const r=e.split(".");if(3!==r.length)return null;const t=r[1],o=t+"=".repeat((4-t.length%4)%4),n=atob(o);return JSON.parse(n)}catch(e){return console.error("Error decoding token:",e),null}},n=e=>{const r=o(e);if(!r||!r.exp)return!0;const t=Math.floor(Date.now()/1e3);return r.exp<t},s=e=>{if(!e)return[];const r=["permissions","roles","authorities","scope"];for(const t of r){const r=e[t];if(Array.isArray(r))return r;if("string"==typeof r)return r.split(" ").filter(Boolean)}return[]},a={key:"bo-remote-token",save:e=>{try{sessionStorage.setItem(a.key,e)}catch(e){console.error("Error saving token:",e)}},get:()=>{try{return sessionStorage.getItem(a.key)}catch(e){return console.error("Error getting token:",e),null}},remove:()=>{try{sessionStorage.removeItem(a.key)}catch(e){console.error("Error removing token:",e)}},clear:()=>{try{sessionStorage.clear()}catch(e){console.error("Error clearing storage:",e)}}}},848:(e,r,t)=>{"use strict";e.exports=t(20)},953:e=>{"use strict";e.exports=require("react")}},r={};function t(o){var n=r[o];if(void 0!==n)return n.exports;var s=r[o]={exports:{}};return e[o](s,s.exports,t),s.exports}t.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return t.d(r,{a:r}),r},t.d=(e,r)=>{for(var o in r)t.o(r,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},t.e=()=>Promise.resolve(),t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var o={};(()=>{"use strict";t.r(o),t.d(o,{RemoteAuthProvider:()=>l,RemoteWrapper:()=>u,Secured:()=>d,createRemoteComponentLoader:()=>p.createRemoteComponentLoader,decodeToken:()=>n.decodeToken,extractPermissions:()=>n.extractPermissions,getRemoteEnvironment:()=>p.getRemoteEnvironment,isRemoteAvailable:()=>p.isRemoteAvailable,isTokenExpired:()=>n.isTokenExpired,loadRemoteModule:()=>p.loadRemoteModule,tokenStorage:()=>n.tokenStorage,useRemoteAuth:()=>c,useRemoteConfig:()=>m});var e=t(848),r=t(953),n=t(433);const s={token:null,isAuthenticated:!1,permissions:[],user:void 0},a=(e,r)=>{switch(r.type){case"SET_TOKEN":const t=(0,n.decodeToken)(r.payload),o=t?(0,n.extractPermissions)(t):[];return{...e,token:r.payload,isAuthenticated:!(0,n.isTokenExpired)(r.payload),permissions:o,user:t?{id:t.sub||"",email:t.email||"",name:t.name||t.email||""}:void 0};case"CLEAR_AUTH":return{...s};case"SET_USER_INFO":return{...e,user:{id:r.payload.sub||"",email:r.payload.email||"",name:r.payload.name||r.payload.email||""}};default:return e}},i=(0,r.createContext)(void 0),l=({children:t,initialToken:o})=>{const[l,c]=(0,r.useReducer)(a,s);(0,r.useEffect)(()=>{const e=n.tokenStorage.get(),r=o||e;r&&!(0,n.isTokenExpired)(r)?c({type:"SET_TOKEN",payload:r}):e&&n.tokenStorage.remove()},[o]);const d={state:l,setToken:e=>{e&&(n.tokenStorage.save(e),c({type:"SET_TOKEN",payload:e}))},clearAuth:()=>{n.tokenStorage.remove(),c({type:"CLEAR_AUTH"})},hasPermission:e=>!!l.isAuthenticated&&l.permissions.includes(e)};return(0,e.jsx)(i.Provider,{value:d,children:t})},c=()=>{const e=(0,r.useContext)(i);if(void 0===e)throw new Error("useRemoteAuth must be used within a RemoteAuthProvider");return e},d=({children:r,permission:t,fallback:o=null})=>{const{hasPermission:n,state:s}=c();return s.isAuthenticated&&n(t)?(0,e.jsx)(e.Fragment,{children:r}):(0,e.jsx)(e.Fragment,{children:o})},u=({children:r,token:t,apiBaseUrl:o,module:n})=>(0,e.jsx)(l,{initialToken:t,children:(0,e.jsx)("div",{"data-remote-module":n,"data-api-base":o,children:r})}),m=()=>{const e=(0,r.useMemo)(()=>({react:{singleton:!0,eager:!1},"react-dom":{singleton:!0,eager:!1},"aurora-web":{singleton:!0,eager:!1},"react-i18next":{singleton:!0,eager:!1},i18next:{singleton:!0,eager:!1}}),[]);return{sharedDependencies:e,getWebpackConfig:(r,t)=>({name:r,filename:"remoteEntry.js",exposes:t,shared:e})}};var p=t(226)})(),module.exports=o})();
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license React
3
+ * react-jsx-runtime.production.min.js
4
+ *
5
+ * Copyright (c) Facebook, Inc. and its affiliates.
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree.
9
+ */
@@ -0,0 +1,48 @@
1
+ export interface RemoteAuthState {
2
+ token: string | null;
3
+ isAuthenticated: boolean;
4
+ permissions: string[];
5
+ user?: {
6
+ id: string;
7
+ email: string;
8
+ name: string;
9
+ };
10
+ }
11
+ export interface RemoteAuthContextValue {
12
+ state: RemoteAuthState;
13
+ setToken: (token: string) => void;
14
+ clearAuth: () => void;
15
+ hasPermission: (permission: string) => boolean;
16
+ }
17
+ export interface AuthProviderProps {
18
+ children: React.ReactNode;
19
+ token?: string;
20
+ apiBaseUrl?: string;
21
+ module?: string;
22
+ }
23
+ export interface SecuredProps {
24
+ children: React.ReactNode;
25
+ permission: string;
26
+ fallback?: React.ReactNode;
27
+ }
28
+ export interface TokenInfo {
29
+ iat?: number;
30
+ exp?: number;
31
+ sub?: string;
32
+ email?: string;
33
+ name?: string;
34
+ permissions?: string[];
35
+ [key: string]: any;
36
+ }
37
+ export interface RemoteConfig {
38
+ name: string;
39
+ url: string;
40
+ module: string;
41
+ }
42
+ export interface SharedDependencies {
43
+ [key: string]: {
44
+ singleton?: boolean;
45
+ requiredVersion?: string;
46
+ eager?: boolean;
47
+ };
48
+ }
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ /**
3
+ * Utilities for Module Federation and remote module management
4
+ */
5
+ /**
6
+ * Dynamically imports a remote module
7
+ */
8
+ export declare const loadRemoteModule: (remoteName: string, moduleName: string) => Promise<any>;
9
+ /**
10
+ * Checks if a remote module is available
11
+ */
12
+ export declare const isRemoteAvailable: (remoteName: string) => Promise<boolean>;
13
+ /**
14
+ * Creates a safe remote component loader with error boundary
15
+ */
16
+ export declare const createRemoteComponentLoader: (remoteName: string, moduleName: string, fallback?: React.ComponentType) => React.LazyExoticComponent<React.ComponentType<any>>;
17
+ /**
18
+ * Environment utilities for remotes
19
+ */
20
+ export declare const getRemoteEnvironment: () => {
21
+ isDevelopment: boolean;
22
+ apiBaseUrl: string;
23
+ frontUrl: string;
24
+ };
@@ -0,0 +1,24 @@
1
+ import { TokenInfo } from '../types';
2
+ /**
3
+ * Safely decodes a JWT token without verification
4
+ * For production use, tokens should be verified server-side
5
+ */
6
+ export declare const decodeToken: (token: string) => TokenInfo | null;
7
+ /**
8
+ * Checks if a token is expired
9
+ */
10
+ export declare const isTokenExpired: (token: string) => boolean;
11
+ /**
12
+ * Extracts permissions from a decoded token
13
+ */
14
+ export declare const extractPermissions: (tokenInfo: TokenInfo) => string[];
15
+ /**
16
+ * Storage utilities for tokens
17
+ */
18
+ export declare const tokenStorage: {
19
+ key: string;
20
+ save: (token: string) => void;
21
+ get: () => string | null;
22
+ remove: () => void;
23
+ clear: () => void;
24
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "max-remotes-helper",
3
+ "version": "1.0.0",
4
+ "description": "Helper library for Module Federation remotes in BO ecosystem",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "webpack --config webpack.config.js --mode=production",
9
+ "dev": "webpack --config webpack.config.js --mode=development --watch",
10
+ "test": "jest",
11
+ "lint": "eslint src/**/*.{ts,tsx}",
12
+ "clean": "rimraf dist"
13
+ },
14
+ "keywords": [
15
+ "module-federation",
16
+ "remotes",
17
+ "react",
18
+ "bo"
19
+ ],
20
+ "author": "BO Team",
21
+ "license": "ISC",
22
+ "dependencies": {
23
+ "react": ">=18.0.0",
24
+ "react-dom": ">=18.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^18.0.37",
28
+ "@types/react-dom": "^18.2.6",
29
+ "@typescript-eslint/eslint-plugin": "^6.17.0",
30
+ "@typescript-eslint/parser": "^6.17.0",
31
+ "clean-webpack-plugin": "^4.0.0",
32
+ "eslint": "^8.56.0",
33
+ "jest": "^29.5.0",
34
+ "rimraf": "^5.0.0",
35
+ "ts-loader": "^9.4.3",
36
+ "typescript": "^5.9.2",
37
+ "webpack": "^5.87.0",
38
+ "webpack-cli": "^5.1.4"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=18.0.0",
42
+ "react-dom": ">=18.0.0"
43
+ }
44
+ }
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { RemoteAuthProvider } from '../context/RemoteAuthContext';
3
+ import { AuthProviderProps } from '../types';
4
+
5
+ /**
6
+ * Wrapper component that provides authentication context to remote components
7
+ * This is the main component that remote applications should use
8
+ */
9
+ export const RemoteWrapper: React.FC<AuthProviderProps> = ({
10
+ children,
11
+ token,
12
+ apiBaseUrl,
13
+ module,
14
+ }) => {
15
+ return (
16
+ <RemoteAuthProvider initialToken={token}>
17
+ <div data-remote-module={module} data-api-base={apiBaseUrl}>
18
+ {children}
19
+ </div>
20
+ </RemoteAuthProvider>
21
+ );
22
+ };
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { SecuredProps } from '../types';
3
+ import { useRemoteAuth } from '../context/RemoteAuthContext';
4
+
5
+ /**
6
+ * Secured component for conditional rendering based on permissions
7
+ * Compatible with Module Federation
8
+ */
9
+ export const Secured: React.FC<SecuredProps> = ({
10
+ children,
11
+ permission,
12
+ fallback = null
13
+ }) => {
14
+ const { hasPermission, state } = useRemoteAuth();
15
+
16
+ // If not authenticated, don't render
17
+ if (!state.isAuthenticated) {
18
+ return <>{fallback}</>;
19
+ }
20
+
21
+ // If has permission, render children
22
+ if (hasPermission(permission)) {
23
+ return <>{children}</>;
24
+ }
25
+
26
+ // Otherwise render fallback
27
+ return <>{fallback}</>;
28
+ };
@@ -0,0 +1,125 @@
1
+ import React, { createContext, useContext, useReducer, useEffect, ReactNode } from 'react';
2
+ import { RemoteAuthState, RemoteAuthContextValue, TokenInfo } from '../types';
3
+ import { decodeToken, extractPermissions, isTokenExpired, tokenStorage } from '../utils/tokenUtils';
4
+
5
+ // Action types
6
+ type AuthAction =
7
+ | { type: 'SET_TOKEN'; payload: string }
8
+ | { type: 'CLEAR_AUTH' }
9
+ | { type: 'SET_USER_INFO'; payload: TokenInfo };
10
+
11
+ // Initial state
12
+ const initialState: RemoteAuthState = {
13
+ token: null,
14
+ isAuthenticated: false,
15
+ permissions: [],
16
+ user: undefined,
17
+ };
18
+
19
+ // Reducer
20
+ const authReducer = (state: RemoteAuthState, action: AuthAction): RemoteAuthState => {
21
+ switch (action.type) {
22
+ case 'SET_TOKEN':
23
+ const tokenInfo = decodeToken(action.payload);
24
+ const permissions = tokenInfo ? extractPermissions(tokenInfo) : [];
25
+
26
+ return {
27
+ ...state,
28
+ token: action.payload,
29
+ isAuthenticated: !isTokenExpired(action.payload),
30
+ permissions,
31
+ user: tokenInfo ? {
32
+ id: tokenInfo.sub || '',
33
+ email: tokenInfo.email || '',
34
+ name: tokenInfo.name || tokenInfo.email || '',
35
+ } : undefined,
36
+ };
37
+
38
+ case 'CLEAR_AUTH':
39
+ return {
40
+ ...initialState,
41
+ };
42
+
43
+ case 'SET_USER_INFO':
44
+ return {
45
+ ...state,
46
+ user: {
47
+ id: action.payload.sub || '',
48
+ email: action.payload.email || '',
49
+ name: action.payload.name || action.payload.email || '',
50
+ },
51
+ };
52
+
53
+ default:
54
+ return state;
55
+ }
56
+ };
57
+
58
+ // Context
59
+ const RemoteAuthContext = createContext<RemoteAuthContextValue | undefined>(undefined);
60
+
61
+ // Provider props
62
+ interface RemoteAuthProviderProps {
63
+ children: ReactNode;
64
+ initialToken?: string;
65
+ }
66
+
67
+ // Provider component
68
+ export const RemoteAuthProvider: React.FC<RemoteAuthProviderProps> = ({
69
+ children,
70
+ initialToken
71
+ }) => {
72
+ const [state, dispatch] = useReducer(authReducer, initialState);
73
+
74
+ // Initialize with token from storage or props
75
+ useEffect(() => {
76
+ const storedToken = tokenStorage.get();
77
+ const tokenToUse = initialToken || storedToken;
78
+
79
+ if (tokenToUse && !isTokenExpired(tokenToUse)) {
80
+ dispatch({ type: 'SET_TOKEN', payload: tokenToUse });
81
+ } else if (storedToken) {
82
+ // Remove expired token
83
+ tokenStorage.remove();
84
+ }
85
+ }, [initialToken]);
86
+
87
+ const setToken = (token: string) => {
88
+ if (!token) return;
89
+
90
+ tokenStorage.save(token);
91
+ dispatch({ type: 'SET_TOKEN', payload: token });
92
+ };
93
+
94
+ const clearAuth = () => {
95
+ tokenStorage.remove();
96
+ dispatch({ type: 'CLEAR_AUTH' });
97
+ };
98
+
99
+ const hasPermission = (permission: string): boolean => {
100
+ if (!state.isAuthenticated) return false;
101
+ return state.permissions.includes(permission);
102
+ };
103
+
104
+ const contextValue: RemoteAuthContextValue = {
105
+ state,
106
+ setToken,
107
+ clearAuth,
108
+ hasPermission,
109
+ };
110
+
111
+ return (
112
+ <RemoteAuthContext.Provider value={contextValue}>
113
+ {children}
114
+ </RemoteAuthContext.Provider>
115
+ );
116
+ };
117
+
118
+ // Hook to use the context
119
+ export const useRemoteAuth = (): RemoteAuthContextValue => {
120
+ const context = useContext(RemoteAuthContext);
121
+ if (context === undefined) {
122
+ throw new Error('useRemoteAuth must be used within a RemoteAuthProvider');
123
+ }
124
+ return context;
125
+ };
@@ -0,0 +1,42 @@
1
+ import { useMemo } from 'react';
2
+ import { SharedDependencies } from '../types';
3
+
4
+ /**
5
+ * Hook that provides standard Module Federation configuration for remotes
6
+ */
7
+ export const useRemoteConfig = () => {
8
+ const sharedDependencies: SharedDependencies = useMemo(() => ({
9
+ react: {
10
+ singleton: true,
11
+ eager: false,
12
+ },
13
+ 'react-dom': {
14
+ singleton: true,
15
+ eager: false,
16
+ },
17
+ 'aurora-web': {
18
+ singleton: true,
19
+ eager: false,
20
+ },
21
+ 'react-i18next': {
22
+ singleton: true,
23
+ eager: false,
24
+ },
25
+ 'i18next': {
26
+ singleton: true,
27
+ eager: false,
28
+ },
29
+ }), []);
30
+
31
+ const getWebpackConfig = (remoteName: string, exposedComponents: Record<string, string>) => ({
32
+ name: remoteName,
33
+ filename: 'remoteEntry.js',
34
+ exposes: exposedComponents,
35
+ shared: sharedDependencies,
36
+ });
37
+
38
+ return {
39
+ sharedDependencies,
40
+ getWebpackConfig,
41
+ };
42
+ };
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ // Main entry point for bo-remotes-helper
2
+
3
+ // Context and Providers
4
+ export { RemoteAuthProvider, useRemoteAuth } from './context/RemoteAuthContext';
5
+
6
+ // Components
7
+ export { Secured } from './components/Secured';
8
+ export { RemoteWrapper } from './components/RemoteWrapper';
9
+
10
+ // Hooks
11
+ export { useRemoteConfig } from './hooks/useRemoteConfig';
12
+
13
+ // Utils
14
+ export {
15
+ decodeToken,
16
+ isTokenExpired,
17
+ extractPermissions,
18
+ tokenStorage
19
+ } from './utils/tokenUtils';
20
+
21
+ export {
22
+ loadRemoteModule,
23
+ isRemoteAvailable,
24
+ createRemoteComponentLoader,
25
+ getRemoteEnvironment
26
+ } from './utils/moduleUtils';
27
+
28
+ // Types
29
+ export type {
30
+ RemoteAuthState,
31
+ RemoteAuthContextValue,
32
+ AuthProviderProps,
33
+ SecuredProps,
34
+ TokenInfo,
35
+ RemoteConfig,
36
+ SharedDependencies,
37
+ } from './types';
@@ -0,0 +1,56 @@
1
+ // Types for bo-remotes-helper
2
+
3
+ export interface RemoteAuthState {
4
+ token: string | null;
5
+ isAuthenticated: boolean;
6
+ permissions: string[];
7
+ user?: {
8
+ id: string;
9
+ email: string;
10
+ name: string;
11
+ };
12
+ }
13
+
14
+ export interface RemoteAuthContextValue {
15
+ state: RemoteAuthState;
16
+ setToken: (token: string) => void;
17
+ clearAuth: () => void;
18
+ hasPermission: (permission: string) => boolean;
19
+ }
20
+
21
+ export interface AuthProviderProps {
22
+ children: React.ReactNode;
23
+ token?: string;
24
+ apiBaseUrl?: string;
25
+ module?: string;
26
+ }
27
+
28
+ export interface SecuredProps {
29
+ children: React.ReactNode;
30
+ permission: string;
31
+ fallback?: React.ReactNode;
32
+ }
33
+
34
+ export interface TokenInfo {
35
+ iat?: number;
36
+ exp?: number;
37
+ sub?: string;
38
+ email?: string;
39
+ name?: string;
40
+ permissions?: string[];
41
+ [key: string]: any;
42
+ }
43
+
44
+ export interface RemoteConfig {
45
+ name: string;
46
+ url: string;
47
+ module: string;
48
+ }
49
+
50
+ export interface SharedDependencies {
51
+ [key: string]: {
52
+ singleton?: boolean;
53
+ requiredVersion?: string;
54
+ eager?: boolean;
55
+ };
56
+ }
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Utilities for Module Federation and remote module management
5
+ */
6
+
7
+ /**
8
+ * Dynamically imports a remote module
9
+ */
10
+ export const loadRemoteModule = async (remoteName: string, moduleName: string) => {
11
+ try {
12
+ // @ts-ignore - Module Federation dynamic import
13
+ const module = await import(`${remoteName}/${moduleName}`);
14
+ return module;
15
+ } catch (error) {
16
+ console.error(`Failed to load remote module ${remoteName}/${moduleName}:`, error);
17
+ throw error;
18
+ }
19
+ };
20
+
21
+ /**
22
+ * Checks if a remote module is available
23
+ */
24
+ export const isRemoteAvailable = async (remoteName: string): Promise<boolean> => {
25
+ try {
26
+ // @ts-ignore - Check if remote is available
27
+ const container = window[remoteName];
28
+ return typeof container?.get === 'function';
29
+ } catch (error) {
30
+ return false;
31
+ }
32
+ };
33
+
34
+ /**
35
+ * Creates a safe remote component loader with error boundary
36
+ */
37
+ export const createRemoteComponentLoader = (
38
+ remoteName: string,
39
+ moduleName: string,
40
+ fallback?: React.ComponentType
41
+ ) => {
42
+ return React.lazy(async () => {
43
+ try {
44
+ const module = await loadRemoteModule(remoteName, moduleName);
45
+ return module;
46
+ } catch (error) {
47
+ console.error(`Remote component loading failed: ${remoteName}/${moduleName}`, error);
48
+
49
+ if (fallback) {
50
+ return { default: fallback };
51
+ }
52
+
53
+ // Return a default error component
54
+ return {
55
+ default: () => React.createElement('div',
56
+ { style: { padding: '20px', color: 'red' } },
57
+ `Failed to load remote component: ${remoteName}/${moduleName}`
58
+ )
59
+ };
60
+ }
61
+ });
62
+ };
63
+
64
+ /**
65
+ * Environment utilities for remotes
66
+ */
67
+ export const getRemoteEnvironment = () => {
68
+ return {
69
+ isDevelopment: process.env.NODE_ENV === 'development',
70
+ apiBaseUrl: process.env.REACT_APP_API_URL || '',
71
+ frontUrl: process.env.REACT_APP_FRONT_URL || '',
72
+ };
73
+ };
@@ -0,0 +1,98 @@
1
+ import { TokenInfo } from '../types';
2
+
3
+ /**
4
+ * Safely decodes a JWT token without verification
5
+ * For production use, tokens should be verified server-side
6
+ */
7
+ export const decodeToken = (token: string): TokenInfo | null => {
8
+ try {
9
+ if (!token) return null;
10
+
11
+ const parts = token.split('.');
12
+ if (parts.length !== 3) return null;
13
+
14
+ const payload = parts[1];
15
+ // Add padding if needed
16
+ const paddedPayload = payload + '='.repeat((4 - payload.length % 4) % 4);
17
+ const decodedPayload = atob(paddedPayload);
18
+
19
+ return JSON.parse(decodedPayload);
20
+ } catch (error) {
21
+ console.error('Error decoding token:', error);
22
+ return null;
23
+ }
24
+ };
25
+
26
+ /**
27
+ * Checks if a token is expired
28
+ */
29
+ export const isTokenExpired = (token: string): boolean => {
30
+ const decoded = decodeToken(token);
31
+ if (!decoded || !decoded.exp) return true;
32
+
33
+ const now = Math.floor(Date.now() / 1000);
34
+ return decoded.exp < now;
35
+ };
36
+
37
+ /**
38
+ * Extracts permissions from a decoded token
39
+ */
40
+ export const extractPermissions = (tokenInfo: TokenInfo): string[] => {
41
+ if (!tokenInfo) return [];
42
+
43
+ // Try different possible permission keys
44
+ const permissionKeys = ['permissions', 'roles', 'authorities', 'scope'];
45
+
46
+ for (const key of permissionKeys) {
47
+ const permissions = tokenInfo[key];
48
+ if (Array.isArray(permissions)) {
49
+ return permissions;
50
+ }
51
+ if (typeof permissions === 'string') {
52
+ // Handle space-separated permissions (like OAuth scope)
53
+ return permissions.split(' ').filter(Boolean);
54
+ }
55
+ }
56
+
57
+ return [];
58
+ };
59
+
60
+ /**
61
+ * Storage utilities for tokens
62
+ */
63
+ export const tokenStorage = {
64
+ key: 'bo-remote-token',
65
+
66
+ save: (token: string): void => {
67
+ try {
68
+ sessionStorage.setItem(tokenStorage.key, token);
69
+ } catch (error) {
70
+ console.error('Error saving token:', error);
71
+ }
72
+ },
73
+
74
+ get: (): string | null => {
75
+ try {
76
+ return sessionStorage.getItem(tokenStorage.key);
77
+ } catch (error) {
78
+ console.error('Error getting token:', error);
79
+ return null;
80
+ }
81
+ },
82
+
83
+ remove: (): void => {
84
+ try {
85
+ sessionStorage.removeItem(tokenStorage.key);
86
+ } catch (error) {
87
+ console.error('Error removing token:', error);
88
+ }
89
+ },
90
+
91
+ clear: (): void => {
92
+ try {
93
+ sessionStorage.clear();
94
+ } catch (error) {
95
+ console.error('Error clearing storage:', error);
96
+ }
97
+ }
98
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "ES6"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "strict": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "module": "esnext",
17
+ "moduleResolution": "node",
18
+ "resolveJsonModule": true,
19
+ "isolatedModules": true,
20
+ "noEmit": false,
21
+ "declaration": true,
22
+ "outDir": "./dist",
23
+ "jsx": "react-jsx"
24
+ },
25
+ "include": [
26
+ "src"
27
+ ],
28
+ "exclude": [
29
+ "node_modules",
30
+ "dist"
31
+ ]
32
+ }
@@ -0,0 +1,38 @@
1
+ const path = require('path');
2
+ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
3
+
4
+ module.exports = {
5
+ entry: './src/index.ts',
6
+ output: {
7
+ filename: 'index.js',
8
+ path: path.resolve(__dirname, 'dist'),
9
+ libraryTarget: 'commonjs2',
10
+ clean: true,
11
+ },
12
+ resolve: {
13
+ extensions: ['.tsx', '.ts', '.js'],
14
+ },
15
+ externals: {
16
+ react: 'react',
17
+ 'react-dom': 'react-dom',
18
+ },
19
+ plugins: [
20
+ new CleanWebpackPlugin(),
21
+ ],
22
+ module: {
23
+ rules: [
24
+ {
25
+ test: /\.tsx?$/,
26
+ use: [
27
+ {
28
+ loader: 'ts-loader',
29
+ options: {
30
+ configFile: 'tsconfig.json',
31
+ },
32
+ },
33
+ ],
34
+ exclude: /node_modules/,
35
+ },
36
+ ],
37
+ },
38
+ };