max-remotes-helper 1.0.1 → 1.0.2

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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * DevButton.tsx
3
+ *
4
+ * Componente de botón simple para componentes de desarrollo (DevWrapper, TokenInput, etc.)
5
+ * No depende de Aurora Web para facilitar la separación del código de desarrollo.
6
+ */
7
+ import React from "react";
8
+ type ButtonVariant = "primary" | "secondary" | "text";
9
+ type ButtonSize = "small" | "medium" | "large";
10
+ interface DevButtonProps {
11
+ label: string;
12
+ onClick?: () => void;
13
+ disabled?: boolean;
14
+ variant?: ButtonVariant;
15
+ size?: ButtonSize;
16
+ className?: string;
17
+ type?: "button" | "submit" | "reset";
18
+ }
19
+ declare const DevButton: React.FC<DevButtonProps>;
20
+ export default DevButton;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * DevCard.tsx
3
+ *
4
+ * Componente de tarjeta simple para componentes de desarrollo.
5
+ * Reemplaza EmptyCard de Aurora Web para eliminar dependencias.
6
+ */
7
+ import React from "react";
8
+ interface DevCardProps {
9
+ children: React.ReactNode;
10
+ className?: string;
11
+ }
12
+ declare const DevCard: React.FC<DevCardProps>;
13
+ export default DevCard;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ interface DevWrapperProps {
3
+ children: React.ReactNode;
4
+ }
5
+ declare const DevWrapper: React.FC<DevWrapperProps>;
6
+ export default DevWrapper;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ interface FloatingTokenButtonProps {
3
+ onClick: () => void;
4
+ }
5
+ declare const FloatingTokenButton: React.FC<FloatingTokenButtonProps>;
6
+ export default FloatingTokenButton;
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
- import { AuthProviderProps } from '../types';
1
+ import React from "react";
2
+ import { AuthProviderProps } from "../types";
3
3
  /**
4
4
  * Wrapper component that provides authentication context to remote components
5
5
  * This is the main component that remote applications should use
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ interface TokenGeneratorProps {
3
+ onSubmit: (token: string) => void;
4
+ onCancel: () => void;
5
+ onClearStorage: () => void;
6
+ currentToken?: string;
7
+ jsonExample?: string;
8
+ defaultDevRoles?: string[];
9
+ }
10
+ declare const TokenGenerator: React.FC<TokenGeneratorProps>;
11
+ export default TokenGenerator;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ interface TokenInputProps {
3
+ onSubmit: (token: string) => void;
4
+ onCancel: () => void;
5
+ onClearStorage: () => void;
6
+ currentToken?: string;
7
+ }
8
+ declare const TokenInput: React.FC<TokenInputProps>;
9
+ export default TokenInput;
@@ -1,5 +1,5 @@
1
- import React, { ReactNode } from 'react';
2
- import { RemoteAuthContextValue } from '../types';
1
+ import React, { ReactNode } from "react";
2
+ import { RemoteAuthContextValue } from "../types";
3
3
  interface RemoteAuthProviderProps {
4
4
  children: ReactNode;
5
5
  initialToken?: string;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
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';
1
+ export { RemoteAuthProvider, useRemoteAuth } from "./context/RemoteAuthContext";
2
+ export { Secured } from "./components/Secured";
3
+ export { RemoteWrapper } from "./components/RemoteWrapper";
4
+ export { default as DevWrapper } from "./components/DevWrapper";
5
+ export { useRemoteConfig } from "./hooks/useRemoteConfig";
6
+ export { decodeToken, isTokenExpired, extractPermissions, tokenStorage, } from "./utils/tokenUtils";
7
+ export { loadRemoteModule, isRemoteAvailable, createRemoteComponentLoader, getRemoteEnvironment, } from "./utils/moduleUtils";
8
+ export type { RemoteAuthState, RemoteAuthContextValue, AuthProviderProps, SecuredProps, TokenInfo, RemoteConfig, SharedDependencies, } from "./types";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! For license information please see index.js.LICENSE.txt */
2
- (()=>{var e={20:(e,t,o)=>{"use strict";var r=o(953),n=Symbol.for("react.element"),s=Symbol.for("react.fragment"),a=Object.prototype.hasOwnProperty,i=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};t.Fragment=s,t.jsx=function(e,t,o){var r,s={},c=null,d=null;for(r in void 0!==o&&(c=""+o),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(d=t.ref),t)a.call(t,r)&&!l.hasOwnProperty(r)&&(s[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===s[r]&&(s[r]=t[r]);return{$$typeof:n,type:e,key:c,ref:d,props:s,_owner:i.current}}},60:(e,t,o)=>{var r={"./moduleUtils":226,"./moduleUtils.ts":226,"./tokenUtils":433,"./tokenUtils.ts":433};function n(e){return Promise.resolve().then(()=>{if(!o.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return o(r[e])})}n.keys=()=>Object.keys(r),n.id=60,e.exports=n},226:(e,t,o)=>{"use strict";o.r(t),o.d(t,{createRemoteComponentLoader:()=>i,getRemoteEnvironment:()=>l,isRemoteAvailable:()=>a,loadRemoteModule:()=>s});var r=o(953),n=o.n(r);const s=async(e,t)=>{try{return await o(60)(`${e}/${t}`)}catch(o){throw console.error(`Failed to load remote module ${e}/${t}:`,o),o}},a=async e=>{try{const t=window[e];return"function"==typeof t?.get}catch(e){return!1}},i=(e,t,o)=>n().lazy(async()=>{try{return await s(e,t)}catch(r){return console.error(`Remote component loading failed: ${e}/${t}`,r),o?{default:o}:{default:()=>n().createElement("div",{style:{padding:"20px",color:"red"}},`Failed to load remote component: ${e}/${t}`)}}}),l=()=>({isDevelopment:!1,apiBaseUrl:process.env.REACT_APP_API_URL||"",frontUrl:process.env.REACT_APP_FRONT_URL||""})},433:(e,t,o)=>{"use strict";o.r(t),o.d(t,{decodeToken:()=>r,extractPermissions:()=>s,isTokenExpired:()=>n,tokenStorage:()=>a});const r=e=>{try{if(!e)return null;const t=e.split(".");if(3!==t.length)return null;const o=t[1],r=o+"=".repeat((4-o.length%4)%4),n=atob(r);return JSON.parse(n)}catch(e){return console.error("Error decoding token:",e),null}},n=e=>{const t=r(e);if(!t||!t.exp)return!0;const o=Math.floor(Date.now()/1e3);return t.exp<o},s=e=>{if(!e)return[];const t=["permissions","roles","authorities","scope"];for(const o of t){const t=e[o];if(Array.isArray(t))return t;if("string"==typeof t)return t.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,t,o)=>{"use strict";e.exports=o(20)},953:e=>{"use strict";e.exports=require("react")}},t={};function o(r){var n=t[r];if(void 0!==n)return n.exports;var s=t[r]={exports:{}};return e[r](s,s.exports,o),s.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.e=()=>Promise.resolve(),o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var r={};(()=>{"use strict";o.r(r),o.d(r,{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=o(848),t=o(953),n=o(433);const s={token:null,isAuthenticated:!1,permissions:[],user:void 0},a=(e,t)=>{switch(t.type){case"SET_TOKEN":const o=(0,n.decodeToken)(t.payload),r=o?(0,n.extractPermissions)(o):[];return{...e,token:t.payload,isAuthenticated:!(0,n.isTokenExpired)(t.payload),permissions:r,user:o?{id:o.sub||"",email:o.email||"",name:o.name||o.email||""}:void 0};case"CLEAR_AUTH":return{...s};case"SET_USER_INFO":return{...e,user:{id:t.payload.sub||"",email:t.payload.email||"",name:t.payload.name||t.payload.email||""}};default:return e}},i=(0,t.createContext)(void 0),l=({children:o,initialToken:r})=>{const[l,c]=(0,t.useReducer)(a,s);(0,t.useEffect)(()=>{if(console.log("🔍 RemoteAuthContext: useEffect triggered",{initialToken:r?r.substring(0,30)+"...":"No initialToken"}),r)console.log("✅ RemoteAuthContext: Using initialToken from prop"),(0,n.isTokenExpired)(r)?(console.log("🔴 RemoteAuthContext: Provided token is expired, clearing auth"),n.tokenStorage.remove(),c({type:"CLEAR_AUTH"})):(console.log("🟢 RemoteAuthContext: Token is valid, dispatching SET_TOKEN"),c({type:"SET_TOKEN",payload:r}),n.tokenStorage.save(r));else{console.log("⚪ RemoteAuthContext: No initialToken, checking stored token");const e=n.tokenStorage.get();e&&!(0,n.isTokenExpired)(e)?(console.log("💾 RemoteAuthContext: Using stored token"),c({type:"SET_TOKEN",payload:e})):e&&(console.log("🗑️ RemoteAuthContext: Stored token expired, clearing"),n.tokenStorage.remove(),c({type:"CLEAR_AUTH"}))}},[r]);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:o})},c=()=>{const e=(0,t.useContext)(i);if(void 0===e)throw new Error("useRemoteAuth must be used within a RemoteAuthProvider");return e},d=({children:t,permission:o,fallback:r=null})=>{const{hasPermission:n,state:s}=c();return s.isAuthenticated&&n(o)?(0,e.jsx)(e.Fragment,{children:t}):(0,e.jsx)(e.Fragment,{children:r})},u=({children:t,token:o,apiBaseUrl:r,module:n})=>(0,e.jsx)(l,{initialToken:o,children:(0,e.jsx)("div",{"data-remote-module":n,"data-api-base":r,children:t})}),m=()=>{const e=(0,t.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:(t,o)=>({name:t,filename:"remoteEntry.js",exposes:o,shared:e})}};var p=o(226)})(),module.exports=r})();
2
+ (()=>{var e={20:(e,t,r)=>{"use strict";var o=r(953),n=Symbol.for("react.element"),a=Symbol.for("react.fragment"),s=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};function c(e,t,r){var o,a={},c=null,d=null;for(o in void 0!==r&&(c=""+r),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(d=t.ref),t)s.call(t,o)&&!l.hasOwnProperty(o)&&(a[o]=t[o]);if(e&&e.defaultProps)for(o in t=e.defaultProps)void 0===a[o]&&(a[o]=t[o]);return{$$typeof:n,type:e,key:c,ref:d,props:a,_owner:i.current}}t.Fragment=a,t.jsx=c,t.jsxs=c},60:(e,t,r)=>{var o={"./moduleUtils":226,"./moduleUtils.ts":226,"./tokenUtils":433,"./tokenUtils.ts":433};function n(e){return Promise.resolve().then(()=>{if(!r.o(o,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r(o[e])})}n.keys=()=>Object.keys(o),n.id=60,e.exports=n},87:(e,t,r)=>{(()=>{var t={20:(e,t,r)=>{"use strict";var o=r(953),n=Symbol.for("react.element"),a=Symbol.for("react.fragment"),s=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};t.Fragment=a,t.jsx=function(e,t,r){var o,a={},c=null,d=null;for(o in void 0!==r&&(c=""+r),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(d=t.ref),t)s.call(t,o)&&!l.hasOwnProperty(o)&&(a[o]=t[o]);if(e&&e.defaultProps)for(o in t=e.defaultProps)void 0===a[o]&&(a[o]=t[o]);return{$$typeof:n,type:e,key:c,ref:d,props:a,_owner:i.current}}},60:(e,t,r)=>{var o={"./moduleUtils":226,"./moduleUtils.ts":226,"./tokenUtils":433,"./tokenUtils.ts":433};function n(e){return Promise.resolve().then(()=>{if(!r.o(o,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r(o[e])})}n.keys=()=>Object.keys(o),n.id=60,e.exports=n},226:(e,t,r)=>{"use strict";r.r(t),r.d(t,{createRemoteComponentLoader:()=>i,getRemoteEnvironment:()=>l,isRemoteAvailable:()=>s,loadRemoteModule:()=>a});var o=r(953),n=r.n(o);const a=async(e,t)=>{try{return await r(60)(`${e}/${t}`)}catch(r){throw console.error(`Failed to load remote module ${e}/${t}:`,r),r}},s=async e=>{try{const t=window[e];return"function"==typeof t?.get}catch(e){return!1}},i=(e,t,r)=>n().lazy(async()=>{try{return await a(e,t)}catch(o){return console.error(`Remote component loading failed: ${e}/${t}`,o),r?{default:r}:{default:()=>n().createElement("div",{style:{padding:"20px",color:"red"}},`Failed to load remote component: ${e}/${t}`)}}}),l=()=>({isDevelopment:!1,apiBaseUrl:process.env.REACT_APP_API_URL||"",frontUrl:process.env.REACT_APP_FRONT_URL||""})},433:(e,t,r)=>{"use strict";r.r(t),r.d(t,{decodeToken:()=>o,extractPermissions:()=>a,isTokenExpired:()=>n,tokenStorage:()=>s});const o=e=>{try{if(!e)return null;const t=e.split(".");if(3!==t.length)return null;const r=t[1],o=r+"=".repeat((4-r.length%4)%4),n=atob(o);return JSON.parse(n)}catch(e){return console.error("Error decoding token:",e),null}},n=e=>{const t=o(e);if(!t||!t.exp)return!0;const r=Math.floor(Date.now()/1e3);return t.exp<r},a=e=>{if(!e)return[];const t=["permissions","roles","authorities","scope"];for(const r of t){const t=e[r];if(Array.isArray(t))return t;if("string"==typeof t)return t.split(" ").filter(Boolean)}return[]},s={key:"bo-remote-token",save:e=>{try{sessionStorage.setItem(s.key,e)}catch(e){console.error("Error saving token:",e)}},get:()=>{try{return sessionStorage.getItem(s.key)}catch(e){return console.error("Error getting token:",e),null}},remove:()=>{try{sessionStorage.removeItem(s.key)}catch(e){console.error("Error removing token:",e)}},clear:()=>{try{sessionStorage.clear()}catch(e){console.error("Error clearing storage:",e)}}}},848:(e,t,r)=>{"use strict";e.exports=r(20)},953:e=>{"use strict";e.exports=r(953)}},o={};function n(e){var r=o[e];if(void 0!==r)return r.exports;var a=o[e]={exports:{}};return t[e](a,a.exports,n),a.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.e=()=>Promise.resolve(),n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var a={};(()=>{"use strict";n.r(a),n.d(a,{RemoteAuthProvider:()=>l,RemoteWrapper:()=>u,Secured:()=>d,createRemoteComponentLoader:()=>g.createRemoteComponentLoader,decodeToken:()=>r.decodeToken,extractPermissions:()=>r.extractPermissions,getRemoteEnvironment:()=>g.getRemoteEnvironment,isRemoteAvailable:()=>g.isRemoteAvailable,isTokenExpired:()=>r.isTokenExpired,loadRemoteModule:()=>g.loadRemoteModule,tokenStorage:()=>r.tokenStorage,useRemoteAuth:()=>c,useRemoteConfig:()=>m});var e=n(848),t=n(953),r=n(433);const o={token:null,isAuthenticated:!1,permissions:[],user:void 0},s=(e,t)=>{switch(t.type){case"SET_TOKEN":const n=(0,r.decodeToken)(t.payload),a=n?(0,r.extractPermissions)(n):[];return{...e,token:t.payload,isAuthenticated:!(0,r.isTokenExpired)(t.payload),permissions:a,user:n?{id:n.sub||"",email:n.email||"",name:n.name||n.email||""}:void 0};case"CLEAR_AUTH":return{...o};case"SET_USER_INFO":return{...e,user:{id:t.payload.sub||"",email:t.payload.email||"",name:t.payload.name||t.payload.email||""}};default:return e}},i=(0,t.createContext)(void 0),l=({children:n,initialToken:a})=>{const[l,c]=(0,t.useReducer)(s,o);(0,t.useEffect)(()=>{if(console.log("🔍 RemoteAuthContext: useEffect triggered",{initialToken:a?a.substring(0,30)+"...":"No initialToken"}),a)console.log("✅ RemoteAuthContext: Using initialToken from prop"),(0,r.isTokenExpired)(a)?(console.log("🔴 RemoteAuthContext: Provided token is expired, clearing auth"),r.tokenStorage.remove(),c({type:"CLEAR_AUTH"})):(console.log("🟢 RemoteAuthContext: Token is valid, dispatching SET_TOKEN"),c({type:"SET_TOKEN",payload:a}),r.tokenStorage.save(a));else{console.log("⚪ RemoteAuthContext: No initialToken, checking stored token");const e=r.tokenStorage.get();e&&!(0,r.isTokenExpired)(e)?(console.log("💾 RemoteAuthContext: Using stored token"),c({type:"SET_TOKEN",payload:e})):e&&(console.log("🗑️ RemoteAuthContext: Stored token expired, clearing"),r.tokenStorage.remove(),c({type:"CLEAR_AUTH"}))}},[a]);const d={state:l,setToken:e=>{e&&(r.tokenStorage.save(e),c({type:"SET_TOKEN",payload:e}))},clearAuth:()=>{r.tokenStorage.remove(),c({type:"CLEAR_AUTH"})},hasPermission:e=>!!l.isAuthenticated&&l.permissions.includes(e)};return(0,e.jsx)(i.Provider,{value:d,children:n})},c=()=>{const e=(0,t.useContext)(i);if(void 0===e)throw new Error("useRemoteAuth must be used within a RemoteAuthProvider");return e},d=({children:t,permission:r,fallback:o=null})=>{const{hasPermission:n,state:a}=c();return a.isAuthenticated&&n(r)?(0,e.jsx)(e.Fragment,{children:t}):(0,e.jsx)(e.Fragment,{children:o})},u=({children:t,token:r,apiBaseUrl:o,module:n})=>(0,e.jsx)(l,{initialToken:r,children:(0,e.jsx)("div",{"data-remote-module":n,"data-api-base":o,children:t})}),m=()=>{const e=(0,t.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:(t,r)=>({name:t,filename:"remoteEntry.js",exposes:r,shared:e})}};var g=n(226)})(),e.exports=a})()},226:(e,t,r)=>{"use strict";r.r(t),r.d(t,{createRemoteComponentLoader:()=>i,getRemoteEnvironment:()=>l,isRemoteAvailable:()=>s,loadRemoteModule:()=>a});var o=r(953),n=r.n(o);const a=async(e,t)=>{try{return await r(60)(`${e}/${t}`)}catch(r){throw console.error(`Failed to load remote module ${e}/${t}:`,r),r}},s=async e=>{try{const t=window[e];return"function"==typeof t?.get}catch(e){return!1}},i=(e,t,r)=>n().lazy(async()=>{try{return await a(e,t)}catch(o){return console.error(`Remote component loading failed: ${e}/${t}`,o),r?{default:r}:{default:()=>n().createElement("div",{style:{padding:"20px",color:"red"}},`Failed to load remote component: ${e}/${t}`)}}}),l=()=>({isDevelopment:!1,apiBaseUrl:process.env.REACT_APP_API_URL||"",frontUrl:process.env.REACT_APP_FRONT_URL||""})},433:(e,t,r)=>{"use strict";r.r(t),r.d(t,{decodeToken:()=>o,extractPermissions:()=>a,isTokenExpired:()=>n,tokenStorage:()=>s});const o=e=>{try{if(!e)return null;const t=e.split(".");if(3!==t.length)return null;const r=t[1],o=r+"=".repeat((4-r.length%4)%4),n=atob(o);return JSON.parse(n)}catch(e){return console.error("Error decoding token:",e),null}},n=e=>{const t=o(e);if(!t||!t.exp)return!0;const r=Math.floor(Date.now()/1e3);return t.exp<r},a=e=>{if(!e)return[];const t=["permissions","roles","authorities","scope"];for(const r of t){const t=e[r];if(Array.isArray(t))return t;if("string"==typeof t)return t.split(" ").filter(Boolean)}return[]},s={key:"bo-remote-token",save:e=>{try{sessionStorage.setItem(s.key,e)}catch(e){console.error("Error saving token:",e)}},get:()=>{try{return sessionStorage.getItem(s.key)}catch(e){return console.error("Error getting token:",e),null}},remove:()=>{try{sessionStorage.removeItem(s.key)}catch(e){console.error("Error removing token:",e)}},clear:()=>{try{sessionStorage.clear()}catch(e){console.error("Error clearing storage:",e)}}}},848:(e,t,r)=>{"use strict";e.exports=r(20)},953:e=>{"use strict";e.exports=require("react")}},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var a=t[o]={exports:{}};return e[o](a,a.exports,r),a.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.e=()=>Promise.resolve(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var o={};(()=>{"use strict";r.r(o),r.d(o,{DevWrapper:()=>y,RemoteAuthProvider:()=>l,RemoteWrapper:()=>u,Secured:()=>d,createRemoteComponentLoader:()=>b.createRemoteComponentLoader,decodeToken:()=>n.decodeToken,extractPermissions:()=>n.extractPermissions,getRemoteEnvironment:()=>b.getRemoteEnvironment,isRemoteAvailable:()=>b.isRemoteAvailable,isTokenExpired:()=>n.isTokenExpired,loadRemoteModule:()=>b.loadRemoteModule,tokenStorage:()=>n.tokenStorage,useRemoteAuth:()=>c,useRemoteConfig:()=>v});var e=r(848),t=r(953),n=r(433);const a={token:null,isAuthenticated:!1,permissions:[],user:void 0},s=(e,t)=>{switch(t.type){case"SET_TOKEN":const r=(0,n.decodeToken)(t.payload),o=r?(0,n.extractPermissions)(r):[];return{...e,token:t.payload,isAuthenticated:!(0,n.isTokenExpired)(t.payload),permissions:o,user:r?{id:r.sub||"",email:r.email||"",name:r.name||r.email||""}:void 0};case"CLEAR_AUTH":return{...a};case"SET_USER_INFO":return{...e,user:{id:t.payload.sub||"",email:t.payload.email||"",name:t.payload.name||t.payload.email||""}};default:return e}},i=(0,t.createContext)(void 0),l=({children:r,initialToken:o})=>{const[l,c]=(0,t.useReducer)(s,a),d="dev"===process.env.REACT_APP_ENVIRONMENT?console.log:()=>{};(0,t.useEffect)(()=>{if(d("🔍 RemoteAuthContext: useEffect triggered",{initialToken:o?o.substring(0,30)+"...":"No initialToken"}),o)d("✅ RemoteAuthContext: Using initialToken from prop"),(0,n.isTokenExpired)(o)?(d("🔴 RemoteAuthContext: Provided token is expired, clearing auth"),n.tokenStorage.remove(),c({type:"CLEAR_AUTH"})):(d("🟢 RemoteAuthContext: Token is valid, dispatching SET_TOKEN"),c({type:"SET_TOKEN",payload:o}),n.tokenStorage.save(o));else{d("⚪ RemoteAuthContext: No initialToken, checking stored token");const e=n.tokenStorage.get();e&&!(0,n.isTokenExpired)(e)?(d("💾 RemoteAuthContext: Using stored token"),c({type:"SET_TOKEN",payload:e})):e&&(d("🗑️ RemoteAuthContext: Stored token expired, clearing"),n.tokenStorage.remove(),c({type:"CLEAR_AUTH"}))}},[o]);const u={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:u,children:r})},c=()=>{const e=(0,t.useContext)(i);if(void 0===e)throw new Error("useRemoteAuth must be used within a RemoteAuthProvider");return e},d=({children:t,permission:r,fallback:o=null})=>{const{hasPermission:n,state:a}=c();return a.isAuthenticated&&n(r)?(0,e.jsx)(e.Fragment,{children:t}):(0,e.jsx)(e.Fragment,{children:o})},u=({children:t,token:r})=>(0,e.jsx)(l,{initialToken:r,children:(0,e.jsx)("div",{children:t})});var m=r(87);const g=({children:t,className:r=""})=>(0,e.jsx)("div",{className:`bg-white rounded-lg shadow-md border border-gray-20 ${r}`,children:t}),p=({label:t,onClick:r,disabled:o=!1,variant:n="primary",size:a="medium",className:s="",type:i="button"})=>(0,e.jsx)("button",{type:i,onClick:r,disabled:o,className:`rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed ${{primary:"bg-gradient-to-br from-blue-40 to-blue-70 hover:from-blue-50 hover:to-blue-90 text-white focus:ring-blue-50 shadow-sm hover:shadow-md",secondary:"bg-gray-20 hover:bg-gray-30 text-gray-80 focus:ring-gray-40 border border-gray-30",text:"bg-transparent hover:bg-gray-10 text-blue-60 hover:text-blue-80 focus:ring-blue-30"}[n]} ${{small:"px-4 py-2 text-sm",medium:"px-5 py-2.5 text-base",large:"px-6 py-3 text-lg"}[a]} ${s}`,children:t}),x=({onSubmit:r,onCancel:o,onClearStorage:n,defaultDevRoles:a=[],currentToken:s=""})=>{const[i,l]=(0,t.useState)(!1),[c,d]=(0,t.useState)(""),[u,m]=(0,t.useState)({user:"usuario@bdsol.com.ar",roles:a,channel:"mf-testing",sessionId:crypto.randomUUID()}),[x,h]=(0,t.useState)(a.join("\n")),f=(e,t)=>{m(r=>({...r,[e]:t}))};return(0,e.jsx)("div",{className:"p-6 w-full overflow-y-auto",children:(0,e.jsx)(g,{className:"p-6",children:(0,e.jsxs)(e.Fragment,{children:[(0,e.jsx)("h2",{className:"text-2xl font-bold text-gray-80 mb-4",children:"🔧 Generador de Tokens (Desarrollo)"}),(0,e.jsx)("p",{className:"text-gray-60 mb-6",children:"Genera un token JWT para desarrollo usando el BFF. Solo disponible cuando ENVIRONMENT=dev."}),c&&(0,e.jsx)("div",{className:"mb-4 p-3 bg-red-10 border border-red-40 text-red-70 rounded",children:c}),(0,e.jsxs)("div",{className:"space-y-4 mb-6",children:[(0,e.jsxs)("div",{children:[(0,e.jsx)("label",{className:"block text-sm font-medium text-gray-70 mb-1",children:"Email del usuario"}),(0,e.jsx)("input",{type:"email",value:u.user,onChange:e=>f("user",e.target.value),className:"w-full p-2 border border-gray-30 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-50",placeholder:"usuario@bdsol.com.ar"})]}),(0,e.jsxs)("div",{children:[(0,e.jsx)("label",{className:"block text-sm font-medium text-gray-70 mb-1",children:"Roles (uno por línea)"}),(0,e.jsx)("textarea",{value:x,onChange:e=>h(e.target.value),className:"w-full h-32 p-2 border border-gray-30 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-50",placeholder:a.join("&#10;")}),(0,e.jsx)("p",{className:"text-xs text-gray-50 mt-1",children:"Roles definidos en src/config/roles.ts"})]}),(0,e.jsxs)("div",{children:[(0,e.jsx)("label",{className:"block text-sm font-medium text-gray-70 mb-1",children:"Channel"}),(0,e.jsx)("input",{type:"text",value:u.channel,onChange:e=>f("channel",e.target.value),className:"w-full p-2 border border-gray-30 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-50",placeholder:"mf-testing"})]}),(0,e.jsxs)("div",{children:[(0,e.jsx)("label",{className:"block text-sm font-medium text-gray-70 mb-1",children:"Session ID"}),(0,e.jsxs)("div",{className:"flex gap-2",children:[(0,e.jsx)("input",{type:"text",value:u.sessionId,onChange:e=>f("sessionId",e.target.value),className:"flex-1 p-2 border border-gray-30 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-50",placeholder:"uuid-session-id"}),(0,e.jsx)(p,{label:"🎲",variant:"primary",size:"small",onClick:()=>{m(e=>({...e,sessionId:crypto.randomUUID()}))},className:"px-3"})]})]})]}),(0,e.jsxs)("div",{className:"flex gap-3 flex-wrap mb-4",children:[(0,e.jsx)(p,{label:i?"Generando...":"🚀 Generar Token",variant:"primary",size:"small",onClick:async()=>{l(!0),d(""),n();try{const e=x.split("\n").map(e=>e.trim()).filter(e=>e.length>0),t={...u,roles:e},o=process.env.REACT_APP_API_URL||"http://localhost:3020",n=await fetch(`${o}/template/api/dev/generate-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!n.ok){const e=await n.json();throw new Error(e.error||"Error al generar token")}const a=await n.json();r(a.token)}catch(e){d(e instanceof Error?e.message:"Error desconocido")}finally{l(!1)}},disabled:i||!u.user.trim()}),(0,e.jsx)(p,{label:"Limpiar almacenamiento",variant:"primary",size:"small",onClick:()=>{n(),alert("SessionStorage limpiado correctamente")}}),(0,e.jsx)(p,{label:"Cancelar",size:"small",variant:"secondary",onClick:o})]}),s&&(0,e.jsxs)("div",{className:"mt-4 p-4 bg-green-50 rounded-lg",children:[(0,e.jsx)("h3",{className:"font-semibold text-green-70 mb-2",children:"✅ Token cargado"}),(0,e.jsx)("p",{className:"text-sm text-green-60",children:"Tienes un token activo en el almacenamiento local."})]})," "]})})})},h=({onSubmit:r,onCancel:o,onClearStorage:n,currentToken:a=""})=>{const[s,i]=(0,t.useState)(a),[l,c]=(0,t.useState)(!0);return(0,e.jsxs)("div",{className:"bg-white rounded-lg shadow-lg p-6 w-full max-w-2xl",children:[(0,e.jsx)("h2",{className:"text-2xl font-bold text-gray-80 mb-4",children:"Modo Desarrollo - Cargar Token JWT"}),(0,e.jsx)("p",{className:"text-gray-60 mb-6",children:"Ingresa el token JWT decodificado (JSON) que normalmente recibiría desde el host:"}),(0,e.jsxs)("div",{className:"mb-4",children:[(0,e.jsx)("textarea",{value:s,onChange:e=>{const t=e.target.value;i(t),l||c(!0)},placeholder:"Pegue aquí el token JWT en formato JSON",className:`w-full h-40 p-3 border rounded-lg font-mono text-sm ${l?"border-gray-30":"border-red-50"} focus:outline-none focus:ring-2 focus:ring-blue-50`}),!l&&(0,e.jsx)("p",{className:"text-red-50 text-sm mt-2",children:"El JSON no es válido. Por favor verifica la sintaxis."})]}),(0,e.jsxs)("div",{className:"flex gap-3 flex-wrap",children:[(0,e.jsx)(p,{label:"Cargar Token",variant:"primary",size:"small",onClick:()=>{try{JSON.parse(s),c(!0),r(s)}catch(e){c(!1)}},disabled:!s.trim()}),(0,e.jsx)(p,{label:"Limpiar",size:"small",variant:"text",onClick:()=>i("")}),(0,e.jsx)(p,{label:"Limpiar Storage",variant:"primary",size:"small",onClick:()=>{n(),i(""),alert("SessionStorage limpiado correctamente")}}),(0,e.jsx)(p,{label:"Cancelar",variant:"secondary",size:"small",onClick:o})]})]})},f=({onClick:t})=>(0,e.jsxs)("button",{onClick:t,className:"fixed bottom-6 right-6 bg-gradient-to-br from-blue-40 to-blue-70 hover:from-blue-50 hover:to-blue-90 text-white p-4 rounded-full shadow-dropdown hover:shadow-2xl transition-all duration-300 z-50 group transform hover:scale-110",title:"Recargar Token",children:[(0,e.jsx)("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,e.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"})}),(0,e.jsx)("span",{className:"absolute bottom-full right-0 mb-3 px-3 py-1 text-xs text-white bg-gray-90/90 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap backdrop-blur-sm",children:"Recargar Token"})]}),y=({children:r})=>{const[o,n]=(0,t.useState)(""),[a,s]=(0,t.useState)(!1),[i,l]=(0,t.useState)(!1),[c,d]=(0,t.useState)(!0),u="dev"===process.env.REACT_APP_ENVIRONMENT&&!o;(0,t.useEffect)(()=>{const e=m.tokenStorage.get();e?(n(e),l(!1)):u&&l(!0)},[u]);const g=e=>{console.log("🔄 DevWrapper: Nuevo token generado",{token:e.substring(0,50)+"..."}),n(e),m.tokenStorage.save(e),console.log("💾 DevWrapper: Token guardado en storage"),s(!1),l(!1)},p=()=>{m.tokenStorage.clear(),n(""),l(u)};return i?(0,e.jsx)("div",{className:"min-h-screen bg-gray-50 flex items-center justify-center p-4",children:(0,e.jsx)(x,{onSubmit:g,onCancel:()=>l(!1),onClearStorage:p,currentToken:o})}):(0,e.jsxs)("div",{className:"min-h-screen bg-gray-50",children:[a&&(0,e.jsx)("div",{className:"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50",children:(0,e.jsxs)("div",{className:"bg-white rounded-lg shadow-lg max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto",children:[(0,e.jsxs)("div",{className:"flex border-b",children:[(0,e.jsx)("button",{onClick:()=>d(!0),className:"flex-1 py-3 px-4 text-sm font-medium "+(c?"bg-blue-50 ":"bg-gray-10 text-gray-70 hover:bg-gray-20"),children:"Generar Token"}),(0,e.jsx)("button",{onClick:()=>d(!1),className:"flex-1 py-3 px-4 text-sm font-medium "+(c?"bg-gray-10 text-gray-70 hover:bg-gray-20":"bg-blue-50 text-gray-50"),children:"Input Manual"})]}),(0,e.jsx)("div",{className:"p-0",children:c?(0,e.jsx)(x,{onSubmit:g,onCancel:()=>s(!1),onClearStorage:p,currentToken:o}):(0,e.jsx)("div",{className:"p-6",children:(0,e.jsx)(h,{onSubmit:g,onCancel:()=>s(!1),onClearStorage:p,currentToken:o})})})]})}),(0,e.jsx)(f,{onClick:()=>{s(!a)}}),(0,e.jsx)(m.RemoteWrapper,{token:o,children:r})]})},v=()=>{const e=(0,t.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:(t,r)=>({name:t,filename:"remoteEntry.js",exposes:r,shared:e})}};var b=r(226)})(),module.exports=o})();
@@ -1,3 +1,5 @@
1
+ /*! For license information please see index.js.LICENSE.txt */
2
+
1
3
  /**
2
4
  * @license React
3
5
  * react-jsx-runtime.production.min.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "max-remotes-helper",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Helper library for Module Federation remotes in BO ecosystem",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,6 +20,7 @@
20
20
  "author": "BO Team",
21
21
  "license": "ISC",
22
22
  "dependencies": {
23
+ "max-remotes-helper": "^1.0.1",
23
24
  "react": ">=18.0.0",
24
25
  "react-dom": ">=18.0.0"
25
26
  },
@@ -0,0 +1,61 @@
1
+ /**
2
+ * DevButton.tsx
3
+ *
4
+ * Componente de botón simple para componentes de desarrollo (DevWrapper, TokenInput, etc.)
5
+ * No depende de Aurora Web para facilitar la separación del código de desarrollo.
6
+ */
7
+
8
+ import React from "react";
9
+
10
+ type ButtonVariant = "primary" | "secondary" | "text";
11
+ type ButtonSize = "small" | "medium" | "large";
12
+
13
+ interface DevButtonProps {
14
+ label: string;
15
+ onClick?: () => void;
16
+ disabled?: boolean;
17
+ variant?: ButtonVariant;
18
+ size?: ButtonSize;
19
+ className?: string;
20
+ type?: "button" | "submit" | "reset";
21
+ }
22
+
23
+ const DevButton: React.FC<DevButtonProps> = ({
24
+ label,
25
+ onClick,
26
+ disabled = false,
27
+ variant = "primary",
28
+ size = "medium",
29
+ className = "",
30
+ type = "button",
31
+ }) => {
32
+ const baseStyles =
33
+ "rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed";
34
+
35
+ const variantStyles = {
36
+ primary:
37
+ "bg-gradient-to-br from-blue-40 to-blue-70 hover:from-blue-50 hover:to-blue-90 text-white focus:ring-blue-50 shadow-sm hover:shadow-md",
38
+ secondary:
39
+ "bg-gray-20 hover:bg-gray-30 text-gray-80 focus:ring-gray-40 border border-gray-30",
40
+ text: "bg-transparent hover:bg-gray-10 text-blue-60 hover:text-blue-80 focus:ring-blue-30",
41
+ };
42
+
43
+ const sizeStyles = {
44
+ small: "px-4 py-2 text-sm",
45
+ medium: "px-5 py-2.5 text-base",
46
+ large: "px-6 py-3 text-lg",
47
+ };
48
+
49
+ return (
50
+ <button
51
+ type={type}
52
+ onClick={onClick}
53
+ disabled={disabled}
54
+ className={`${baseStyles} ${variantStyles[variant]} ${sizeStyles[size]} ${className}`}
55
+ >
56
+ {label}
57
+ </button>
58
+ );
59
+ };
60
+
61
+ export default DevButton;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * DevCard.tsx
3
+ *
4
+ * Componente de tarjeta simple para componentes de desarrollo.
5
+ * Reemplaza EmptyCard de Aurora Web para eliminar dependencias.
6
+ */
7
+
8
+ import React from "react";
9
+
10
+ interface DevCardProps {
11
+ children: React.ReactNode;
12
+ className?: string;
13
+ }
14
+
15
+ const DevCard: React.FC<DevCardProps> = ({ children, className = "" }) => {
16
+ return (
17
+ <div
18
+ className={`bg-white rounded-lg shadow-md border border-gray-20 ${className}`}
19
+ >
20
+ {children}
21
+ </div>
22
+ );
23
+ };
24
+
25
+ export default DevCard;
@@ -0,0 +1,128 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { RemoteWrapper, tokenStorage } from "max-remotes-helper";
3
+ import TokenGenerator from "./TokenGenerator";
4
+ import TokenInput from "./TokenInput";
5
+ import FloatingTokenButton from "./FloatingTokenButton";
6
+
7
+ interface DevWrapperProps {
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ const DevWrapper: React.FC<DevWrapperProps> = ({ children }) => {
12
+ const [currentToken, setCurrentToken] = useState<string>("");
13
+ const [showTokenInput, setShowTokenInput] = useState<boolean>(false);
14
+ const [needsToken, setNeedsToken] = useState<boolean>(false);
15
+ const [showGenerator, setShowGenerator] = useState<boolean>(true);
16
+
17
+ // Detectar si estamos en desarrollo y sin host
18
+ const isDevEnvironment = process.env.REACT_APP_ENVIRONMENT === "dev";
19
+ const shouldShowTokenScreen = isDevEnvironment && !currentToken;
20
+
21
+ useEffect(() => {
22
+ const savedToken = tokenStorage.get();
23
+ if (savedToken) {
24
+ setCurrentToken(savedToken);
25
+ setNeedsToken(false);
26
+ } else if (shouldShowTokenScreen) {
27
+ setNeedsToken(true);
28
+ }
29
+ }, [shouldShowTokenScreen]);
30
+
31
+ const handleTokenSubmit = (token: string) => {
32
+ console.log("🔄 DevWrapper: Nuevo token generado", {
33
+ token: token.substring(0, 50) + "...",
34
+ });
35
+ setCurrentToken(token);
36
+ tokenStorage.save(token);
37
+ console.log("💾 DevWrapper: Token guardado en storage");
38
+ setShowTokenInput(false);
39
+ setNeedsToken(false);
40
+ };
41
+
42
+ const handleClearStorage = () => {
43
+ tokenStorage.clear();
44
+ setCurrentToken("");
45
+ setNeedsToken(shouldShowTokenScreen);
46
+ };
47
+
48
+ const toggleTokenInput = () => {
49
+ setShowTokenInput(!showTokenInput);
50
+ };
51
+
52
+ // Si estamos en desarrollo, sin host y sin token, mostrar pantalla de generación
53
+ if (needsToken) {
54
+ return (
55
+ <div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
56
+ <TokenGenerator
57
+ onSubmit={handleTokenSubmit}
58
+ onCancel={() => setNeedsToken(false)}
59
+ onClearStorage={handleClearStorage}
60
+ currentToken={currentToken}
61
+ />
62
+ </div>
63
+ );
64
+ }
65
+
66
+ return (
67
+ <div className="min-h-screen bg-gray-50">
68
+ {/* Token Input/Generator Modal */}
69
+ {showTokenInput && (
70
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
71
+ <div className="bg-white rounded-lg shadow-lg max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
72
+ {/* Tabs para cambiar entre generador y input manual */}
73
+ <div className="flex border-b">
74
+ <button
75
+ onClick={() => setShowGenerator(true)}
76
+ className={`flex-1 py-3 px-4 text-sm font-medium ${
77
+ showGenerator
78
+ ? "bg-blue-50 "
79
+ : "bg-gray-10 text-gray-70 hover:bg-gray-20"
80
+ }`}
81
+ >
82
+ Generar Token
83
+ </button>
84
+ <button
85
+ onClick={() => setShowGenerator(false)}
86
+ className={`flex-1 py-3 px-4 text-sm font-medium ${
87
+ !showGenerator
88
+ ? "bg-blue-50 text-gray-50"
89
+ : "bg-gray-10 text-gray-70 hover:bg-gray-20"
90
+ }`}
91
+ >
92
+ Input Manual
93
+ </button>
94
+ </div>
95
+
96
+ <div className="p-0">
97
+ {showGenerator ? (
98
+ <TokenGenerator
99
+ onSubmit={handleTokenSubmit}
100
+ onCancel={() => setShowTokenInput(false)}
101
+ onClearStorage={handleClearStorage}
102
+ currentToken={currentToken}
103
+ />
104
+ ) : (
105
+ <div className="p-6">
106
+ <TokenInput
107
+ onSubmit={handleTokenSubmit}
108
+ onCancel={() => setShowTokenInput(false)}
109
+ onClearStorage={handleClearStorage}
110
+ currentToken={currentToken}
111
+ />
112
+ </div>
113
+ )}
114
+ </div>
115
+ </div>
116
+ </div>
117
+ )}
118
+
119
+ {/* Floating Token Button */}
120
+ <FloatingTokenButton onClick={toggleTokenInput} />
121
+
122
+ {/* Main Content wrapped with RemoteWrapper */}
123
+ <RemoteWrapper token={currentToken}>{children}</RemoteWrapper>
124
+ </div>
125
+ );
126
+ };
127
+
128
+ export default DevWrapper;
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+
3
+ interface FloatingTokenButtonProps {
4
+ onClick: () => void;
5
+ }
6
+
7
+ const FloatingTokenButton: React.FC<FloatingTokenButtonProps> = ({
8
+ onClick,
9
+ }) => {
10
+ return (
11
+ <button
12
+ onClick={onClick}
13
+ className="fixed bottom-6 right-6 bg-gradient-to-br from-blue-40 to-blue-70 hover:from-blue-50 hover:to-blue-90 text-white p-4 rounded-full shadow-dropdown hover:shadow-2xl transition-all duration-300 z-50 group transform hover:scale-110"
14
+ title="Recargar Token"
15
+ >
16
+ <svg
17
+ className="w-5 h-5"
18
+ fill="none"
19
+ stroke="currentColor"
20
+ viewBox="0 0 24 24"
21
+ >
22
+ <path
23
+ strokeLinecap="round"
24
+ strokeLinejoin="round"
25
+ strokeWidth={2}
26
+ d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"
27
+ />
28
+ </svg>
29
+ <span className="absolute bottom-full right-0 mb-3 px-3 py-1 text-xs text-white bg-gray-90/90 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap backdrop-blur-sm">
30
+ {"Recargar Token"}
31
+ </span>
32
+ </button>
33
+ );
34
+ };
35
+
36
+ export default FloatingTokenButton;
@@ -1,6 +1,6 @@
1
- import React from 'react';
2
- import { RemoteAuthProvider } from '../context/RemoteAuthContext';
3
- import { AuthProviderProps } from '../types';
1
+ import React from "react";
2
+ import { RemoteAuthProvider } from "../context/RemoteAuthContext";
3
+ import { AuthProviderProps } from "../types";
4
4
 
5
5
  /**
6
6
  * Wrapper component that provides authentication context to remote components
@@ -9,14 +9,10 @@ import { AuthProviderProps } from '../types';
9
9
  export const RemoteWrapper: React.FC<AuthProviderProps> = ({
10
10
  children,
11
11
  token,
12
- apiBaseUrl,
13
- module,
14
12
  }) => {
15
13
  return (
16
14
  <RemoteAuthProvider initialToken={token}>
17
- <div data-remote-module={module} data-api-base={apiBaseUrl}>
18
- {children}
19
- </div>
15
+ <div>{children}</div>
20
16
  </RemoteAuthProvider>
21
17
  );
22
- };
18
+ };
@@ -0,0 +1,227 @@
1
+ import React, { useState } from "react";
2
+
3
+ import DevCard from "./DevCard";
4
+ import DevButton from "./DevButton";
5
+
6
+ interface TokenGeneratorProps {
7
+ onSubmit: (token: string) => void;
8
+ onCancel: () => void;
9
+ onClearStorage: () => void;
10
+ currentToken?: string;
11
+ jsonExample?: string;
12
+ defaultDevRoles?: string[];
13
+ }
14
+
15
+ interface TokenPayload {
16
+ user: string;
17
+ roles: string[];
18
+ channel: string;
19
+ sessionId: string;
20
+ }
21
+
22
+ const TokenGenerator: React.FC<TokenGeneratorProps> = ({
23
+ onSubmit,
24
+ onCancel,
25
+ onClearStorage,
26
+ defaultDevRoles = [],
27
+ currentToken = "",
28
+ }) => {
29
+ const [loading, setLoading] = useState(false);
30
+ const [error, setError] = useState<string>("");
31
+
32
+ // Estado del formulario
33
+ const [formData, setFormData] = useState<TokenPayload>({
34
+ user: "usuario@bdsol.com.ar",
35
+ roles: defaultDevRoles,
36
+ channel: "mf-testing",
37
+ sessionId: crypto.randomUUID(),
38
+ });
39
+
40
+ const [rolesText, setRolesText] = useState(defaultDevRoles.join("\n"));
41
+
42
+ const generateToken = async () => {
43
+ setLoading(true);
44
+ setError("");
45
+ onClearStorage();
46
+ try {
47
+ // Parsear roles del textarea
48
+ const roles = rolesText
49
+ .split("\n")
50
+ .map((role) => role.trim())
51
+ .filter((role) => role.length > 0);
52
+
53
+ const payload = {
54
+ ...formData,
55
+ roles,
56
+ };
57
+
58
+ const bffUrl = process.env.REACT_APP_API_URL || "http://localhost:3020";
59
+ const response = await fetch(
60
+ `${bffUrl}/template/api/dev/generate-token`,
61
+ {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ },
66
+ body: JSON.stringify(payload),
67
+ },
68
+ );
69
+
70
+ if (!response.ok) {
71
+ const errorData = await response.json();
72
+ throw new Error(errorData.error || "Error al generar token");
73
+ }
74
+
75
+ const data = await response.json();
76
+ onSubmit(data.token);
77
+ } catch (err) {
78
+ setError(err instanceof Error ? err.message : "Error desconocido");
79
+ } finally {
80
+ setLoading(false);
81
+ }
82
+ };
83
+
84
+ const handleInputChange = (field: keyof TokenPayload, value: string) => {
85
+ setFormData((prev) => ({
86
+ ...prev,
87
+ [field]: value,
88
+ }));
89
+ };
90
+
91
+ const generateNewSessionId = () => {
92
+ setFormData((prev) => ({
93
+ ...prev,
94
+ sessionId: crypto.randomUUID(),
95
+ }));
96
+ };
97
+
98
+ const handleClearStorageClick = () => {
99
+ onClearStorage();
100
+ alert("SessionStorage limpiado correctamente");
101
+ };
102
+
103
+ return (
104
+ <div className="p-6 w-full overflow-y-auto">
105
+ <DevCard className="p-6">
106
+ <>
107
+ <h2 className="text-2xl font-bold text-gray-80 mb-4">
108
+ 🔧 Generador de Tokens (Desarrollo)
109
+ </h2>
110
+ <p className="text-gray-60 mb-6">
111
+ Genera un token JWT para desarrollo usando el BFF. Solo disponible
112
+ cuando ENVIRONMENT=dev.
113
+ </p>
114
+ {error && (
115
+ <div className="mb-4 p-3 bg-red-10 border border-red-40 text-red-70 rounded">
116
+ {error}
117
+ </div>
118
+ )}
119
+ <div className="space-y-4 mb-6">
120
+ {/* Email */}
121
+ <div>
122
+ <label className="block text-sm font-medium text-gray-70 mb-1">
123
+ Email del usuario
124
+ </label>
125
+ <input
126
+ type="email"
127
+ value={formData.user}
128
+ onChange={(e) => handleInputChange("user", e.target.value)}
129
+ className="w-full p-2 border border-gray-30 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-50"
130
+ placeholder="usuario@bdsol.com.ar"
131
+ />
132
+ </div>
133
+
134
+ {/* Roles */}
135
+ <div>
136
+ <label className="block text-sm font-medium text-gray-70 mb-1">
137
+ Roles (uno por línea)
138
+ </label>
139
+ <textarea
140
+ value={rolesText}
141
+ onChange={(e) => setRolesText(e.target.value)}
142
+ className="w-full h-32 p-2 border border-gray-30 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-50"
143
+ placeholder={defaultDevRoles.join("&#10;")}
144
+ />
145
+ <p className="text-xs text-gray-50 mt-1">
146
+ Roles definidos en src/config/roles.ts
147
+ </p>
148
+ </div>
149
+
150
+ {/* Channel */}
151
+ <div>
152
+ <label className="block text-sm font-medium text-gray-70 mb-1">
153
+ Channel
154
+ </label>
155
+ <input
156
+ type="text"
157
+ value={formData.channel}
158
+ onChange={(e) => handleInputChange("channel", e.target.value)}
159
+ className="w-full p-2 border border-gray-30 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-50"
160
+ placeholder="mf-testing"
161
+ />
162
+ </div>
163
+
164
+ {/* Session ID */}
165
+ <div>
166
+ <label className="block text-sm font-medium text-gray-70 mb-1">
167
+ Session ID
168
+ </label>
169
+ <div className="flex gap-2">
170
+ <input
171
+ type="text"
172
+ value={formData.sessionId}
173
+ onChange={(e) =>
174
+ handleInputChange("sessionId", e.target.value)
175
+ }
176
+ className="flex-1 p-2 border border-gray-30 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-50"
177
+ placeholder="uuid-session-id"
178
+ />
179
+ <DevButton
180
+ label="🎲"
181
+ variant="primary"
182
+ size="small"
183
+ onClick={generateNewSessionId}
184
+ className="px-3"
185
+ />
186
+ </div>
187
+ </div>
188
+ </div>
189
+ <div className="flex gap-3 flex-wrap mb-4">
190
+ <DevButton
191
+ label={loading ? "Generando..." : "🚀 Generar Token"}
192
+ variant="primary"
193
+ size="small"
194
+ onClick={generateToken}
195
+ disabled={loading || !formData.user.trim()}
196
+ />
197
+ <DevButton
198
+ label="Limpiar almacenamiento"
199
+ variant="primary"
200
+ size="small"
201
+ onClick={handleClearStorageClick}
202
+ />
203
+ <DevButton
204
+ label="Cancelar"
205
+ size="small"
206
+ variant="secondary"
207
+ onClick={onCancel}
208
+ />
209
+ </div>
210
+ {/* Información del token actual */}
211
+ {currentToken && (
212
+ <div className="mt-4 p-4 bg-green-50 rounded-lg">
213
+ <h3 className="font-semibold text-green-70 mb-2">
214
+ ✅ Token cargado
215
+ </h3>
216
+ <p className="text-sm text-green-60">
217
+ Tienes un token activo en el almacenamiento local.
218
+ </p>
219
+ </div>
220
+ )}{" "}
221
+ </>
222
+ </DevCard>
223
+ </div>
224
+ );
225
+ };
226
+
227
+ export default TokenGenerator;
@@ -0,0 +1,103 @@
1
+ import React, { useState } from "react";
2
+ import DevButton from "./DevButton";
3
+
4
+ interface TokenInputProps {
5
+ onSubmit: (token: string) => void;
6
+ onCancel: () => void;
7
+ onClearStorage: () => void;
8
+ currentToken?: string;
9
+ }
10
+
11
+ const TokenInput: React.FC<TokenInputProps> = ({
12
+ onSubmit,
13
+ onCancel,
14
+ onClearStorage,
15
+ currentToken = "",
16
+ }) => {
17
+ const [token, setToken] = useState(currentToken);
18
+ const [isValidJson, setIsValidJson] = useState(true);
19
+
20
+ const validateAndSubmit = () => {
21
+ try {
22
+ JSON.parse(token);
23
+ setIsValidJson(true);
24
+ onSubmit(token);
25
+ } catch (error) {
26
+ setIsValidJson(false);
27
+ }
28
+ };
29
+
30
+ const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
31
+ const value = e.target.value;
32
+ setToken(value);
33
+
34
+ // Reset validation state when user starts typing
35
+ if (!isValidJson) {
36
+ setIsValidJson(true);
37
+ }
38
+ };
39
+
40
+ const handleClearStorageClick = () => {
41
+ onClearStorage();
42
+ setToken("");
43
+ alert("SessionStorage limpiado correctamente");
44
+ };
45
+
46
+ return (
47
+ <div className="bg-white rounded-lg shadow-lg p-6 w-full max-w-2xl">
48
+ <h2 className="text-2xl font-bold text-gray-80 mb-4">
49
+ Modo Desarrollo - Cargar Token JWT
50
+ </h2>
51
+ <p className="text-gray-60 mb-6">
52
+ Ingresa el token JWT decodificado (JSON) que normalmente recibiría desde
53
+ el host:
54
+ </p>
55
+
56
+ <div className="mb-4">
57
+ <textarea
58
+ value={token}
59
+ onChange={handleTextareaChange}
60
+ placeholder="Pegue aquí el token JWT en formato JSON"
61
+ className={`w-full h-40 p-3 border rounded-lg font-mono text-sm ${
62
+ !isValidJson ? "border-red-50" : "border-gray-30"
63
+ } focus:outline-none focus:ring-2 focus:ring-blue-50`}
64
+ />
65
+ {!isValidJson && (
66
+ <p className="text-red-50 text-sm mt-2">
67
+ El JSON no es válido. Por favor verifica la sintaxis.
68
+ </p>
69
+ )}
70
+ </div>
71
+
72
+ <div className="flex gap-3 flex-wrap">
73
+ <DevButton
74
+ label="Cargar Token"
75
+ variant="primary"
76
+ size="small"
77
+ onClick={validateAndSubmit}
78
+ disabled={!token.trim()}
79
+ />
80
+ <DevButton
81
+ label="Limpiar"
82
+ size="small"
83
+ variant="text"
84
+ onClick={() => setToken("")}
85
+ />
86
+ <DevButton
87
+ label="Limpiar Storage"
88
+ variant="primary"
89
+ size="small"
90
+ onClick={handleClearStorageClick}
91
+ />
92
+ <DevButton
93
+ label="Cancelar"
94
+ variant="secondary"
95
+ size="small"
96
+ onClick={onCancel}
97
+ />
98
+ </div>
99
+ </div>
100
+ );
101
+ };
102
+
103
+ export default TokenInput;
@@ -86,25 +86,39 @@ export const RemoteAuthProvider: React.FC<RemoteAuthProviderProps> = ({
86
86
  initialToken,
87
87
  }) => {
88
88
  const [state, dispatch] = useReducer(authReducer, initialState);
89
+ const isDev = process.env.REACT_APP_ENVIRONMENT === "dev";
90
+ const log = isDev ? console.log : () => {};
89
91
 
90
92
  // Initialize with token from storage or props
91
93
  useEffect(() => {
94
+ log("🔍 RemoteAuthContext: useEffect triggered", {
95
+ initialToken: initialToken
96
+ ? initialToken.substring(0, 30) + "..."
97
+ : "No initialToken",
98
+ });
99
+
92
100
  // Prioritize the prop token over stored token when initialToken is provided
93
101
  if (initialToken) {
102
+ log("✅ RemoteAuthContext: Using initialToken from prop");
94
103
  if (!isTokenExpired(initialToken)) {
104
+ log("🟢 RemoteAuthContext: Token is valid, dispatching SET_TOKEN");
95
105
  dispatch({ type: "SET_TOKEN", payload: initialToken });
96
106
  tokenStorage.save(initialToken); // Also update storage
97
107
  } else {
108
+ log("🔴 RemoteAuthContext: Provided token is expired, clearing auth");
98
109
  // If provided token is expired, clear it
99
110
  tokenStorage.remove();
100
111
  dispatch({ type: "CLEAR_AUTH" });
101
112
  }
102
113
  } else {
114
+ log("⚪ RemoteAuthContext: No initialToken, checking stored token");
103
115
  // Fallback to stored token when no initialToken prop
104
116
  const storedToken = tokenStorage.get();
105
117
  if (storedToken && !isTokenExpired(storedToken)) {
118
+ log("💾 RemoteAuthContext: Using stored token");
106
119
  dispatch({ type: "SET_TOKEN", payload: storedToken });
107
120
  } else if (storedToken) {
121
+ log("🗑️ RemoteAuthContext: Stored token expired, clearing");
108
122
  // Remove expired stored token
109
123
  tokenStorage.remove();
110
124
  dispatch({ type: "CLEAR_AUTH" });
package/src/index.ts CHANGED
@@ -1,29 +1,30 @@
1
1
  // Main entry point for bo-remotes-helper
2
2
 
3
3
  // Context and Providers
4
- export { RemoteAuthProvider, useRemoteAuth } from './context/RemoteAuthContext';
4
+ export { RemoteAuthProvider, useRemoteAuth } from "./context/RemoteAuthContext";
5
5
 
6
6
  // Components
7
- export { Secured } from './components/Secured';
8
- export { RemoteWrapper } from './components/RemoteWrapper';
7
+ export { Secured } from "./components/Secured";
8
+ export { RemoteWrapper } from "./components/RemoteWrapper";
9
+ export { default as DevWrapper } from "./components/DevWrapper";
9
10
 
10
11
  // Hooks
11
- export { useRemoteConfig } from './hooks/useRemoteConfig';
12
+ export { useRemoteConfig } from "./hooks/useRemoteConfig";
12
13
 
13
14
  // Utils
14
- export {
15
- decodeToken,
16
- isTokenExpired,
17
- extractPermissions,
18
- tokenStorage
19
- } from './utils/tokenUtils';
15
+ export {
16
+ decodeToken,
17
+ isTokenExpired,
18
+ extractPermissions,
19
+ tokenStorage,
20
+ } from "./utils/tokenUtils";
20
21
 
21
- export {
22
- loadRemoteModule,
23
- isRemoteAvailable,
24
- createRemoteComponentLoader,
25
- getRemoteEnvironment
26
- } from './utils/moduleUtils';
22
+ export {
23
+ loadRemoteModule,
24
+ isRemoteAvailable,
25
+ createRemoteComponentLoader,
26
+ getRemoteEnvironment,
27
+ } from "./utils/moduleUtils";
27
28
 
28
29
  // Types
29
30
  export type {
@@ -34,4 +35,4 @@ export type {
34
35
  TokenInfo,
35
36
  RemoteConfig,
36
37
  SharedDependencies,
37
- } from './types';
38
+ } from "./types";