@azure/msal-react
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -5,2 +5,37 @@ { | ||
{ | ||
"date": "Tue, 08 Feb 2022 00:41:07 GMT", | ||
"tag": "@azure/msal-react_v1.3.0", | ||
"version": "1.3.0", | ||
"comments": { | ||
"minor": [ | ||
{ | ||
"author": "thomas.norling@microsoft.com", | ||
"package": "@azure/msal-react", | ||
"commit": "67ce881faa2a6931320541d99de2efe4e9ba6a3f", | ||
"comment": "useMsalAuthentication hook acquires a token if user is already signed in #4280" | ||
}, | ||
{ | ||
"author": "beachball", | ||
"package": "@azure/msal-react", | ||
"comment": "Bump @azure/msal-browser to v2.22.0", | ||
"commit": "639253acbc825e1f19ca712bc72dce8da149e3e8" | ||
} | ||
], | ||
"none": [ | ||
{ | ||
"author": "kamausamuel11@gmail.com", | ||
"package": "@azure/msal-react", | ||
"commit": "5767008f8ef9f3b05aeba421cc4aa6c6bb616e86", | ||
"comment": "fix: update package-lock files" | ||
}, | ||
{ | ||
"author": "thomas.norling@microsoft.com", | ||
"package": "@azure/msal-react", | ||
"commit": "19f76b1d9cd2b0f1a4a7e4765c8ee9e7e17f3d59", | ||
"comment": "Test changes #4383" | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"date": "Tue, 04 Jan 2022 00:20:29 GMT", | ||
@@ -7,0 +42,0 @@ "tag": "@azure/msal-react_v1.2.0", |
# Change Log - @azure/msal-react | ||
This log was last generated on Tue, 04 Jan 2022 00:20:29 GMT and should not be manually modified. | ||
This log was last generated on Tue, 08 Feb 2022 00:41:07 GMT and should not be manually modified. | ||
<!-- Start content --> | ||
## 1.3.0 | ||
Tue, 08 Feb 2022 00:41:07 GMT | ||
### Minor changes | ||
- useMsalAuthentication hook acquires a token if user is already signed in #4280 (thomas.norling@microsoft.com) | ||
- Bump @azure/msal-browser to v2.22.0 | ||
## 1.2.0 | ||
@@ -8,0 +17,0 @@ |
@@ -7,2 +7,2 @@ import { AccountInfo } from "@azure/msal-browser"; | ||
*/ | ||
export declare function useAccount(accountIdentifiers: AccountIdentifiers): AccountInfo | null; | ||
export declare function useAccount(accountIdentifiers?: AccountIdentifiers): AccountInfo | null; |
@@ -1,5 +0,6 @@ | ||
import { PopupRequest, RedirectRequest, SsoSilentRequest, InteractionType, AuthenticationResult, AuthError } from "@azure/msal-browser"; | ||
import { PopupRequest, RedirectRequest, SsoSilentRequest, InteractionType, AuthenticationResult, AuthError, SilentRequest } from "@azure/msal-browser"; | ||
import { AccountIdentifiers } from "../types/AccountIdentifiers"; | ||
export declare type MsalAuthenticationResult = { | ||
login: Function; | ||
login: (callbackInteractionType?: InteractionType | undefined, callbackRequest?: PopupRequest | RedirectRequest | SilentRequest) => Promise<AuthenticationResult | null>; | ||
acquireToken: (callbackInteractionType?: InteractionType | undefined, callbackRequest?: SilentRequest | undefined) => Promise<AuthenticationResult | null>; | ||
result: AuthenticationResult | null; | ||
@@ -9,4 +10,5 @@ error: AuthError | null; | ||
/** | ||
* Invokes a login call if a user is not currently signed-in. Failed logins can be retried using the login callback returned. | ||
* Optionally provide a request object to be used in the login call. | ||
* If a user is not currently signed in this hook invokes a login. Failed logins can be retried using the login callback returned. | ||
* If a user is currently signed in this hook attempts to acquire a token. Subsequent token requests can use the acquireToken callback returned. | ||
* Optionally provide a request object to be used in the login/acquireToken call. | ||
* Optionally provide a specific user that should be logged in. | ||
@@ -13,0 +15,0 @@ * @param interactionType |
@@ -63,6 +63,28 @@ 'use strict'; | ||
} | ||
function getAccountByIdentifiers(allAccounts, accountIdentifiers) { | ||
if (allAccounts.length > 0 && (accountIdentifiers.homeAccountId || accountIdentifiers.localAccountId || accountIdentifiers.username)) { | ||
const matchedAccounts = allAccounts.filter(accountObj => { | ||
if (accountIdentifiers.username && accountIdentifiers.username.toLowerCase() !== accountObj.username.toLowerCase()) { | ||
return false; | ||
} | ||
if (accountIdentifiers.homeAccountId && accountIdentifiers.homeAccountId.toLowerCase() !== accountObj.homeAccountId.toLowerCase()) { | ||
return false; | ||
} | ||
if (accountIdentifiers.localAccountId && accountIdentifiers.localAccountId.toLowerCase() !== accountObj.localAccountId.toLowerCase()) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return matchedAccounts[0] || null; | ||
} else { | ||
return null; | ||
} | ||
} | ||
/* eslint-disable header/header */ | ||
const name = "@azure/msal-react"; | ||
const version = "1.2.0"; | ||
const version = "1.3.0"; | ||
@@ -85,3 +107,3 @@ /* | ||
const [accounts, setAccounts] = React.useState([]); // State hook to store in progress value | ||
const [accounts, setAccounts] = React.useState(() => instance.getAllAccounts()); // State hook to store in progress value | ||
@@ -182,57 +204,5 @@ const [inProgress, setInProgress] = React.useState(msalBrowser.InteractionStatus.Startup); // Mutable object used in the event callback | ||
function getAccount(instance, accountIdentifiers) { | ||
const allAccounts = instance.getAllAccounts(); | ||
if (allAccounts.length > 0 && (accountIdentifiers.homeAccountId || accountIdentifiers.localAccountId || accountIdentifiers.username)) { | ||
const matchedAccounts = allAccounts.filter(accountObj => { | ||
if (accountIdentifiers.username && accountIdentifiers.username.toLowerCase() !== accountObj.username.toLowerCase()) { | ||
return false; | ||
} | ||
if (accountIdentifiers.homeAccountId && accountIdentifiers.homeAccountId.toLowerCase() !== accountObj.homeAccountId.toLowerCase()) { | ||
return false; | ||
} | ||
if (accountIdentifiers.localAccountId && accountIdentifiers.localAccountId.toLowerCase() !== accountObj.localAccountId.toLowerCase()) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return matchedAccounts[0] || null; | ||
} else { | ||
return null; | ||
} | ||
} | ||
/** | ||
* Given 1 or more accountIdentifiers, returns the Account object if the user is signed-in | ||
* @param accountIdentifiers | ||
*/ | ||
function useAccount(accountIdentifiers) { | ||
const { | ||
instance, | ||
inProgress | ||
} = useMsal(); | ||
const initialStateValue = inProgress === msalBrowser.InteractionStatus.Startup ? null : getAccount(instance, accountIdentifiers); | ||
const [account, setAccount] = React.useState(initialStateValue); | ||
React.useEffect(() => { | ||
const currentAccount = getAccount(instance, accountIdentifiers); | ||
if (!msalBrowser.AccountEntity.accountInfoIsEqual(account, currentAccount, true)) { | ||
setAccount(currentAccount); | ||
} | ||
}, [inProgress, accountIdentifiers, instance, account]); | ||
return account; | ||
} | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
function isAuthenticated(allAccounts, account, matchAccount) { | ||
function isAuthenticated(allAccounts, matchAccount) { | ||
if (matchAccount && (matchAccount.username || matchAccount.homeAccountId || matchAccount.localAccountId)) { | ||
return !!account; | ||
return !!getAccountByIdentifiers(allAccounts, matchAccount); | ||
} | ||
@@ -250,11 +220,8 @@ | ||
const { | ||
accounts: allAccounts, | ||
inProgress | ||
accounts: allAccounts | ||
} = useMsal(); | ||
const account = useAccount(matchAccount || {}); | ||
const initialStateValue = inProgress === msalBrowser.InteractionStatus.Startup ? false : isAuthenticated(allAccounts, account, matchAccount); | ||
const [hasAuthenticated, setHasAuthenticated] = React.useState(initialStateValue); | ||
const [hasAuthenticated, setHasAuthenticated] = React.useState(() => isAuthenticated(allAccounts, matchAccount)); | ||
React.useEffect(() => { | ||
setHasAuthenticated(isAuthenticated(allAccounts, account, matchAccount)); | ||
}, [allAccounts, account, matchAccount]); | ||
setHasAuthenticated(isAuthenticated(allAccounts, matchAccount)); | ||
}, [allAccounts, matchAccount]); | ||
return hasAuthenticated; | ||
@@ -331,5 +298,78 @@ } | ||
*/ | ||
function getAccount(instance, accountIdentifiers) { | ||
if (!accountIdentifiers || !accountIdentifiers.homeAccountId && !accountIdentifiers.localAccountId && !accountIdentifiers.username) { | ||
// If no account identifiers are provided, return active account | ||
return instance.getActiveAccount(); | ||
} | ||
return getAccountByIdentifiers(instance.getAllAccounts(), accountIdentifiers); | ||
} | ||
/** | ||
* Invokes a login call if a user is not currently signed-in. Failed logins can be retried using the login callback returned. | ||
* Optionally provide a request object to be used in the login call. | ||
* Given 1 or more accountIdentifiers, returns the Account object if the user is signed-in | ||
* @param accountIdentifiers | ||
*/ | ||
function useAccount(accountIdentifiers) { | ||
const { | ||
instance, | ||
inProgress, | ||
logger | ||
} = useMsal(); | ||
const [account, setAccount] = React.useState(() => getAccount(instance, accountIdentifiers)); | ||
React.useEffect(() => { | ||
setAccount(currentAccount => { | ||
const nextAccount = getAccount(instance, accountIdentifiers); | ||
if (!msalBrowser.AccountEntity.accountInfoIsEqual(currentAccount, nextAccount, true)) { | ||
logger.info("useAccount - Updating account"); | ||
return nextAccount; | ||
} | ||
return currentAccount; | ||
}); | ||
}, [inProgress, accountIdentifiers, instance, logger]); | ||
return account; | ||
} | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
const ReactAuthErrorMessage = { | ||
invalidInteractionType: { | ||
code: "invalid_interaction_type", | ||
desc: "The provided interaction type is invalid." | ||
}, | ||
unableToFallbackToInteraction: { | ||
code: "unable_to_fallback_to_interaction", | ||
desc: "Interaction is required but another interaction is already in progress. Please try again when the current interaction is complete." | ||
} | ||
}; | ||
class ReactAuthError extends msalBrowser.AuthError { | ||
constructor(errorCode, errorMessage) { | ||
super(errorCode, errorMessage); | ||
Object.setPrototypeOf(this, ReactAuthError.prototype); | ||
this.name = "ReactAuthError"; | ||
} | ||
static createInvalidInteractionTypeError() { | ||
return new ReactAuthError(ReactAuthErrorMessage.invalidInteractionType.code, ReactAuthErrorMessage.invalidInteractionType.desc); | ||
} | ||
static createUnableToFallbackToInteractionError() { | ||
return new ReactAuthError(ReactAuthErrorMessage.unableToFallbackToInteraction.code, ReactAuthErrorMessage.unableToFallbackToInteraction.desc); | ||
} | ||
} | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
/** | ||
* If a user is not currently signed in this hook invokes a login. Failed logins can be retried using the login callback returned. | ||
* If a user is currently signed in this hook attempts to acquire a token. Subsequent token requests can use the acquireToken callback returned. | ||
* Optionally provide a request object to be used in the login/acquireToken call. | ||
* Optionally provide a specific user that should be logged in. | ||
@@ -348,4 +388,24 @@ * @param interactionType | ||
const isAuthenticated = useIsAuthenticated(accountIdentifiers); | ||
const [[result, error], setResponse] = React.useState([null, null]); | ||
const [hasBeenCalled, setHasBeenCalled] = React.useState(false); | ||
const account = useAccount(accountIdentifiers); | ||
const [[result, error], setResponse] = React.useState([null, null]); // Boolean used to check if interaction is in progress in acquireTokenSilent fallback. Use Ref instead of state to prevent acquireToken function from being regenerated on each change to interactionInProgress value | ||
const interactionInProgress = React.useRef(inProgress !== msalBrowser.InteractionStatus.None); | ||
React.useEffect(() => { | ||
interactionInProgress.current = inProgress !== msalBrowser.InteractionStatus.None; | ||
}, [inProgress]); // Flag used to control when the hook calls login/acquireToken | ||
const shouldAcquireToken = React.useRef(true); | ||
React.useEffect(() => { | ||
if (!!error) { | ||
// Errors should be handled by consuming component | ||
shouldAcquireToken.current = false; | ||
return; | ||
} | ||
if (!!result) { | ||
// Token has already been acquired, consuming component/application is responsible for renewing | ||
shouldAcquireToken.current = false; | ||
return; | ||
} | ||
}, [error, result]); | ||
const login = React.useCallback(async (callbackInteractionType, callbackRequest) => { | ||
@@ -370,5 +430,55 @@ const loginType = callbackInteractionType || interactionType; | ||
default: | ||
throw "Invalid interaction type provided."; | ||
throw ReactAuthError.createInvalidInteractionTypeError(); | ||
} | ||
}, [instance, interactionType, authenticationRequest, logger]); | ||
const acquireToken = React.useCallback(async (callbackInteractionType, callbackRequest) => { | ||
const fallbackInteractionType = callbackInteractionType || interactionType; | ||
let tokenRequest; | ||
if (callbackRequest) { | ||
logger.trace("useMsalAuthentication - acquireToken - Using request provided in the callback"); | ||
tokenRequest = { ...callbackRequest | ||
}; | ||
} else if (authenticationRequest) { | ||
logger.trace("useMsalAuthentication - acquireToken - Using request provided in the hook"); | ||
tokenRequest = { ...authenticationRequest, | ||
scopes: authenticationRequest.scopes || msalBrowser.OIDC_DEFAULT_SCOPES | ||
}; | ||
} else { | ||
logger.trace("useMsalAuthentication - acquireToken - No request object provided, using default request."); | ||
tokenRequest = { | ||
scopes: msalBrowser.OIDC_DEFAULT_SCOPES | ||
}; | ||
} | ||
if (!tokenRequest.account && account) { | ||
logger.trace("useMsalAuthentication - acquireToken - Attaching account to request"); | ||
tokenRequest.account = account; | ||
} | ||
const getToken = async () => { | ||
logger.verbose("useMsalAuthentication - Calling acquireTokenSilent"); | ||
return instance.acquireTokenSilent(tokenRequest).catch(async e => { | ||
if (e instanceof msalBrowser.InteractionRequiredAuthError) { | ||
if (!interactionInProgress.current) { | ||
logger.error("useMsalAuthentication - Interaction required, falling back to interaction"); | ||
return login(fallbackInteractionType, tokenRequest); | ||
} else { | ||
logger.error("useMsalAuthentication - Interaction required but is already in progress. Please try again, if needed, after interaction completes."); | ||
throw ReactAuthError.createUnableToFallbackToInteractionError(); | ||
} | ||
} | ||
throw e; | ||
}); | ||
}; | ||
return getToken().then(response => { | ||
setResponse([response, null]); | ||
return response; | ||
}).catch(e => { | ||
setResponse([null, e]); | ||
throw e; | ||
}); | ||
}, [instance, interactionType, authenticationRequest, logger, account, login]); | ||
React.useEffect(() => { | ||
@@ -403,14 +513,23 @@ const callbackId = instance.addEventCallback(message => { | ||
React.useEffect(() => { | ||
if (!hasBeenCalled && !error && !isAuthenticated && inProgress === msalBrowser.InteractionStatus.None) { | ||
logger.info("useMsalAuthentication - No user is authenticated, attempting to login"); // Ensure login is only called one time from within this hook, any subsequent login attempts should use the callback returned | ||
if (shouldAcquireToken.current && inProgress === msalBrowser.InteractionStatus.None) { | ||
shouldAcquireToken.current = false; | ||
setHasBeenCalled(true); | ||
login().catch(() => { | ||
// Errors are handled by the event handler above | ||
return; | ||
}); | ||
if (!isAuthenticated) { | ||
logger.info("useMsalAuthentication - No user is authenticated, attempting to login"); | ||
login().catch(() => { | ||
// Errors are saved in state above | ||
return; | ||
}); | ||
} else if (account) { | ||
logger.info("useMsalAuthentication - User is authenticated, attempting to acquire token"); | ||
acquireToken().catch(() => { | ||
// Errors are saved in state above | ||
return; | ||
}); | ||
} | ||
} | ||
}, [isAuthenticated, inProgress, error, hasBeenCalled, login, logger]); | ||
}, [isAuthenticated, account, inProgress, login, acquireToken, logger]); | ||
return { | ||
login, | ||
acquireToken, | ||
result, | ||
@@ -417,0 +536,0 @@ error |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=require("react"),n=(e=t)&&"object"==typeof e&&"default"in e?e.default:e,o=require("@azure/msal-browser");const r=t.createContext({instance:o.stubbedPublicClientApplication,inProgress:o.InteractionStatus.None,accounts:[],logger:new o.Logger({})}),c=r.Consumer;function a(e,t){return"function"==typeof e?e(t):e}const s=()=>t.useContext(r);function u(e,t){const n=e.getAllAccounts();return n.length>0&&(t.homeAccountId||t.localAccountId||t.username)&&n.filter(e=>!(t.username&&t.username.toLowerCase()!==e.username.toLowerCase()||t.homeAccountId&&t.homeAccountId.toLowerCase()!==e.homeAccountId.toLowerCase()||t.localAccountId&&t.localAccountId.toLowerCase()!==e.localAccountId.toLowerCase()))[0]||null}function l(e){const{instance:n,inProgress:r}=s(),c=r===o.InteractionStatus.Startup?null:u(n,e),[a,l]=t.useState(c);return t.useEffect(()=>{const t=u(n,e);o.AccountEntity.accountInfoIsEqual(a,t,!0)||l(t)},[r,e,n,a]),a}function i(e,t,n){return n&&(n.username||n.homeAccountId||n.localAccountId)?!!t:e.length>0}function d(e){const{accounts:n,inProgress:r}=s(),c=l(e||{}),a=r!==o.InteractionStatus.Startup&&i(n,c,e),[u,d]=t.useState(a);return t.useEffect(()=>{d(i(n,c,e))},[n,c,e]),u}function p(e,n,r){const{instance:c,inProgress:a,logger:u}=s(),l=d(r),[[i,p],m]=t.useState([null,null]),[E,I]=t.useState(!1),v=t.useCallback(async(t,r)=>{const a=r||n;switch(t||e){case o.InteractionType.Popup:return u.verbose("useMsalAuthentication - Calling loginPopup"),c.loginPopup(a);case o.InteractionType.Redirect:return u.verbose("useMsalAuthentication - Calling loginRedirect"),c.loginRedirect(a).then(null);case o.InteractionType.Silent:return u.verbose("useMsalAuthentication - Calling ssoSilent"),c.ssoSilent(a);default:throw"Invalid interaction type provided."}},[c,e,n,u]);return t.useEffect(()=>{const e=c.addEventCallback(e=>{switch(e.eventType){case o.EventType.LOGIN_SUCCESS:case o.EventType.SSO_SILENT_SUCCESS:e.payload&&m([e.payload,null]);break;case o.EventType.LOGIN_FAILURE:case o.EventType.SSO_SILENT_FAILURE:e.error&&m([null,e.error])}});return u.verbose("useMsalAuthentication - Registered event callback with id: "+e),()=>{e&&(u.verbose("useMsalAuthentication - Removing event callback "+e),c.removeEventCallback(e))}},[c,u]),t.useEffect(()=>{E||p||l||a!==o.InteractionStatus.None||(u.info("useMsalAuthentication - No user is authenticated, attempting to login"),I(!0),v().catch(()=>{}))},[l,a,p,E,v,u]),{login:v,result:i,error:p}}exports.AuthenticatedTemplate=function({username:e,homeAccountId:r,localAccountId:c,children:u}){const l=s();return d(t.useMemo(()=>({username:e,homeAccountId:r,localAccountId:c}),[e,r,c]))&&l.inProgress!==o.InteractionStatus.Startup?n.createElement(n.Fragment,null,a(u,l)):null},exports.MsalAuthenticationTemplate=function({interactionType:e,username:r,homeAccountId:c,localAccountId:u,authenticationRequest:l,loadingComponent:i,errorComponent:m,children:E}){const I=t.useMemo(()=>({username:r,homeAccountId:c,localAccountId:u}),[r,c,u]),v=s(),S=p(e,l,I),g=d(I);if(S.error&&v.inProgress===o.InteractionStatus.None){if(m)return n.createElement(m,Object.assign({},S));throw S.error}return g?n.createElement(n.Fragment,null,a(E,S)):i&&v.inProgress!==o.InteractionStatus.None?n.createElement(i,Object.assign({},v)):null},exports.MsalConsumer=c,exports.MsalContext=r,exports.MsalProvider=function({instance:e,children:c}){t.useEffect(()=>{e.initializeWrapperLibrary(o.WrapperSKU.React,"1.2.0")},[e]);const a=t.useMemo(()=>e.getLogger().clone("@azure/msal-react","1.2.0"),[e]),[s,u]=t.useState([]),[l,i]=t.useState(o.InteractionStatus.Startup),d=t.useRef(l);return t.useEffect(()=>{const t=e.addEventCallback(t=>{switch(t.eventType){case o.EventType.ACCOUNT_ADDED:case o.EventType.ACCOUNT_REMOVED:case o.EventType.LOGIN_SUCCESS:case o.EventType.SSO_SILENT_SUCCESS:case o.EventType.HANDLE_REDIRECT_END:case o.EventType.LOGIN_FAILURE:case o.EventType.SSO_SILENT_FAILURE:case o.EventType.LOGOUT_END:case o.EventType.ACQUIRE_TOKEN_SUCCESS:case o.EventType.ACQUIRE_TOKEN_FAILURE:const t=e.getAllAccounts();!function(e,t){if(e.length!==t.length)return!1;const n=[...t];return e.every(e=>{const t=n.shift();return!(!e||!t)&&e.homeAccountId===t.homeAccountId&&e.localAccountId===t.localAccountId&&e.username===t.username})}(t,s)?(a.info("MsalProvider - updating account state"),u(t)):a.info("MsalProvider - no account changes")}});return a.verbose("MsalProvider - Registered event callback with id: "+t),()=>{t&&(a.verbose("MsalProvider - Removing event callback "+t),e.removeEventCallback(t))}},[e,s,a]),t.useEffect(()=>{const t=e.addEventCallback(e=>{const t=o.EventMessageUtils.getInteractionStatusFromEvent(e,d.current);null!==t&&(a.info(`MsalProvider - ${e.eventType} results in setting inProgress from ${d.current} to ${t}`),d.current=t,i(t))});return a.verbose("MsalProvider - Registered event callback with id: "+t),e.handleRedirectPromise().catch(()=>{}).finally(()=>{d.current===o.InteractionStatus.Startup&&(d.current=o.InteractionStatus.None,i(o.InteractionStatus.None))}),()=>{t&&(a.verbose("MsalProvider - Removing event callback "+t),e.removeEventCallback(t))}},[e,a]),n.createElement(r.Provider,{value:{instance:e,inProgress:l,accounts:s,logger:a}},c)},exports.UnauthenticatedTemplate=function({username:e,homeAccountId:r,localAccountId:c,children:u}){const l=s();return d(t.useMemo(()=>({username:e,homeAccountId:r,localAccountId:c}),[e,r,c]))||l.inProgress===o.InteractionStatus.Startup||l.inProgress===o.InteractionStatus.HandleRedirect?null:n.createElement(n.Fragment,null,a(u,l))},exports.useAccount=l,exports.useIsAuthenticated=d,exports.useMsal=s,exports.useMsalAuthentication=p,exports.version="1.2.0",exports.withMsal=e=>{const t=t=>{const o=s();return n.createElement(e,Object.assign({},t,{msalContext:o}))};return t.displayName=`withMsal(${e.displayName||e.name||"Component"})`,t}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=require("react"),n=(e=t)&&"object"==typeof e&&"default"in e?e.default:e,r=require("@azure/msal-browser");const o=t.createContext({instance:r.stubbedPublicClientApplication,inProgress:r.InteractionStatus.None,accounts:[],logger:new r.Logger({})}),c=o.Consumer;function a(e,t){return"function"==typeof e?e(t):e}function s(e,t){return e.length>0&&(t.homeAccountId||t.localAccountId||t.username)&&e.filter(e=>!(t.username&&t.username.toLowerCase()!==e.username.toLowerCase()||t.homeAccountId&&t.homeAccountId.toLowerCase()!==e.homeAccountId.toLowerCase()||t.localAccountId&&t.localAccountId.toLowerCase()!==e.localAccountId.toLowerCase()))[0]||null}const u=()=>t.useContext(o);function i(e,t){return t&&(t.username||t.homeAccountId||t.localAccountId)?!!s(e,t):e.length>0}function l(e){const{accounts:n}=u(),[r,o]=t.useState(()=>i(n,e));return t.useEffect(()=>{o(i(n,e))},[n,e]),r}function d(e,t){return t&&(t.homeAccountId||t.localAccountId||t.username)?s(e.getAllAccounts(),t):e.getActiveAccount()}function p(e){const{instance:n,inProgress:o,logger:c}=u(),[a,s]=t.useState(()=>d(n,e));return t.useEffect(()=>{s(t=>{const o=d(n,e);return r.AccountEntity.accountInfoIsEqual(t,o,!0)?t:(c.info("useAccount - Updating account"),o)})},[o,e,n,c]),a}class h extends r.AuthError{constructor(e,t){super(e,t),Object.setPrototypeOf(this,h.prototype),this.name="ReactAuthError"}static createInvalidInteractionTypeError(){return new h("invalid_interaction_type","The provided interaction type is invalid.")}static createUnableToFallbackToInteractionError(){return new h("unable_to_fallback_to_interaction","Interaction is required but another interaction is already in progress. Please try again when the current interaction is complete.")}}function E(e,n,o){const{instance:c,inProgress:a,logger:s}=u(),i=l(o),d=p(o),[[E,I],A]=t.useState([null,null]),g=t.useRef(a!==r.InteractionStatus.None);t.useEffect(()=>{g.current=a!==r.InteractionStatus.None},[a]);const m=t.useRef(!0);t.useEffect(()=>{(I||E)&&(m.current=!1)},[I,E]);const v=t.useCallback(async(t,o)=>{const a=o||n;switch(t||e){case r.InteractionType.Popup:return s.verbose("useMsalAuthentication - Calling loginPopup"),c.loginPopup(a);case r.InteractionType.Redirect:return s.verbose("useMsalAuthentication - Calling loginRedirect"),c.loginRedirect(a).then(null);case r.InteractionType.Silent:return s.verbose("useMsalAuthentication - Calling ssoSilent"),c.ssoSilent(a);default:throw h.createInvalidInteractionTypeError()}},[c,e,n,s]),f=t.useCallback(async(t,o)=>{const a=t||e;let u;return o?(s.trace("useMsalAuthentication - acquireToken - Using request provided in the callback"),u={...o}):n?(s.trace("useMsalAuthentication - acquireToken - Using request provided in the hook"),u={...n,scopes:n.scopes||r.OIDC_DEFAULT_SCOPES}):(s.trace("useMsalAuthentication - acquireToken - No request object provided, using default request."),u={scopes:r.OIDC_DEFAULT_SCOPES}),!u.account&&d&&(s.trace("useMsalAuthentication - acquireToken - Attaching account to request"),u.account=d),(async()=>(s.verbose("useMsalAuthentication - Calling acquireTokenSilent"),c.acquireTokenSilent(u).catch(async e=>{if(e instanceof r.InteractionRequiredAuthError){if(g.current)throw s.error("useMsalAuthentication - Interaction required but is already in progress. Please try again, if needed, after interaction completes."),h.createUnableToFallbackToInteractionError();return s.error("useMsalAuthentication - Interaction required, falling back to interaction"),v(a,u)}throw e})))().then(e=>(A([e,null]),e)).catch(e=>{throw A([null,e]),e})},[c,e,n,s,d,v]);return t.useEffect(()=>{const e=c.addEventCallback(e=>{switch(e.eventType){case r.EventType.LOGIN_SUCCESS:case r.EventType.SSO_SILENT_SUCCESS:e.payload&&A([e.payload,null]);break;case r.EventType.LOGIN_FAILURE:case r.EventType.SSO_SILENT_FAILURE:e.error&&A([null,e.error])}});return s.verbose("useMsalAuthentication - Registered event callback with id: "+e),()=>{e&&(s.verbose("useMsalAuthentication - Removing event callback "+e),c.removeEventCallback(e))}},[c,s]),t.useEffect(()=>{m.current&&a===r.InteractionStatus.None&&(m.current=!1,i?d&&(s.info("useMsalAuthentication - User is authenticated, attempting to acquire token"),f().catch(()=>{})):(s.info("useMsalAuthentication - No user is authenticated, attempting to login"),v().catch(()=>{})))},[i,d,a,v,f,s]),{login:v,acquireToken:f,result:E,error:I}}exports.AuthenticatedTemplate=function({username:e,homeAccountId:o,localAccountId:c,children:s}){const i=u();return l(t.useMemo(()=>({username:e,homeAccountId:o,localAccountId:c}),[e,o,c]))&&i.inProgress!==r.InteractionStatus.Startup?n.createElement(n.Fragment,null,a(s,i)):null},exports.MsalAuthenticationTemplate=function({interactionType:e,username:o,homeAccountId:c,localAccountId:s,authenticationRequest:i,loadingComponent:d,errorComponent:p,children:h}){const I=t.useMemo(()=>({username:o,homeAccountId:c,localAccountId:s}),[o,c,s]),A=u(),g=E(e,i,I),m=l(I);if(g.error&&A.inProgress===r.InteractionStatus.None){if(p)return n.createElement(p,Object.assign({},g));throw g.error}return m?n.createElement(n.Fragment,null,a(h,g)):d&&A.inProgress!==r.InteractionStatus.None?n.createElement(d,Object.assign({},A)):null},exports.MsalConsumer=c,exports.MsalContext=o,exports.MsalProvider=function({instance:e,children:c}){t.useEffect(()=>{e.initializeWrapperLibrary(r.WrapperSKU.React,"1.3.0")},[e]);const a=t.useMemo(()=>e.getLogger().clone("@azure/msal-react","1.3.0"),[e]),[s,u]=t.useState(()=>e.getAllAccounts()),[i,l]=t.useState(r.InteractionStatus.Startup),d=t.useRef(i);return t.useEffect(()=>{const t=e.addEventCallback(t=>{switch(t.eventType){case r.EventType.ACCOUNT_ADDED:case r.EventType.ACCOUNT_REMOVED:case r.EventType.LOGIN_SUCCESS:case r.EventType.SSO_SILENT_SUCCESS:case r.EventType.HANDLE_REDIRECT_END:case r.EventType.LOGIN_FAILURE:case r.EventType.SSO_SILENT_FAILURE:case r.EventType.LOGOUT_END:case r.EventType.ACQUIRE_TOKEN_SUCCESS:case r.EventType.ACQUIRE_TOKEN_FAILURE:const t=e.getAllAccounts();!function(e,t){if(e.length!==t.length)return!1;const n=[...t];return e.every(e=>{const t=n.shift();return!(!e||!t)&&e.homeAccountId===t.homeAccountId&&e.localAccountId===t.localAccountId&&e.username===t.username})}(t,s)?(a.info("MsalProvider - updating account state"),u(t)):a.info("MsalProvider - no account changes")}});return a.verbose("MsalProvider - Registered event callback with id: "+t),()=>{t&&(a.verbose("MsalProvider - Removing event callback "+t),e.removeEventCallback(t))}},[e,s,a]),t.useEffect(()=>{const t=e.addEventCallback(e=>{const t=r.EventMessageUtils.getInteractionStatusFromEvent(e,d.current);null!==t&&(a.info(`MsalProvider - ${e.eventType} results in setting inProgress from ${d.current} to ${t}`),d.current=t,l(t))});return a.verbose("MsalProvider - Registered event callback with id: "+t),e.handleRedirectPromise().catch(()=>{}).finally(()=>{d.current===r.InteractionStatus.Startup&&(d.current=r.InteractionStatus.None,l(r.InteractionStatus.None))}),()=>{t&&(a.verbose("MsalProvider - Removing event callback "+t),e.removeEventCallback(t))}},[e,a]),n.createElement(o.Provider,{value:{instance:e,inProgress:i,accounts:s,logger:a}},c)},exports.UnauthenticatedTemplate=function({username:e,homeAccountId:o,localAccountId:c,children:s}){const i=u();return l(t.useMemo(()=>({username:e,homeAccountId:o,localAccountId:c}),[e,o,c]))||i.inProgress===r.InteractionStatus.Startup||i.inProgress===r.InteractionStatus.HandleRedirect?null:n.createElement(n.Fragment,null,a(s,i))},exports.useAccount=p,exports.useIsAuthenticated=l,exports.useMsal=u,exports.useMsalAuthentication=E,exports.version="1.3.0",exports.withMsal=e=>{const t=t=>{const r=u();return n.createElement(e,Object.assign({},t,{msalContext:r}))};return t.displayName=`withMsal(${e.displayName||e.name||"Component"})`,t}; | ||
//# sourceMappingURL=msal-react.cjs.production.min.js.map |
import React__default, { createContext, useEffect, useMemo, useState, useRef, useContext, useCallback } from 'react'; | ||
import { stubbedPublicClientApplication, InteractionStatus, Logger, WrapperSKU, EventType, EventMessageUtils, AccountEntity, InteractionType } from '@azure/msal-browser'; | ||
import { stubbedPublicClientApplication, InteractionStatus, Logger, WrapperSKU, EventType, EventMessageUtils, AccountEntity, AuthError, InteractionType, InteractionRequiredAuthError, OIDC_DEFAULT_SCOPES } from '@azure/msal-browser'; | ||
@@ -56,6 +56,28 @@ /* | ||
} | ||
function getAccountByIdentifiers(allAccounts, accountIdentifiers) { | ||
if (allAccounts.length > 0 && (accountIdentifiers.homeAccountId || accountIdentifiers.localAccountId || accountIdentifiers.username)) { | ||
const matchedAccounts = allAccounts.filter(accountObj => { | ||
if (accountIdentifiers.username && accountIdentifiers.username.toLowerCase() !== accountObj.username.toLowerCase()) { | ||
return false; | ||
} | ||
if (accountIdentifiers.homeAccountId && accountIdentifiers.homeAccountId.toLowerCase() !== accountObj.homeAccountId.toLowerCase()) { | ||
return false; | ||
} | ||
if (accountIdentifiers.localAccountId && accountIdentifiers.localAccountId.toLowerCase() !== accountObj.localAccountId.toLowerCase()) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return matchedAccounts[0] || null; | ||
} else { | ||
return null; | ||
} | ||
} | ||
/* eslint-disable header/header */ | ||
const name = "@azure/msal-react"; | ||
const version = "1.2.0"; | ||
const version = "1.3.0"; | ||
@@ -78,3 +100,3 @@ /* | ||
const [accounts, setAccounts] = useState([]); // State hook to store in progress value | ||
const [accounts, setAccounts] = useState(() => instance.getAllAccounts()); // State hook to store in progress value | ||
@@ -175,57 +197,5 @@ const [inProgress, setInProgress] = useState(InteractionStatus.Startup); // Mutable object used in the event callback | ||
function getAccount(instance, accountIdentifiers) { | ||
const allAccounts = instance.getAllAccounts(); | ||
if (allAccounts.length > 0 && (accountIdentifiers.homeAccountId || accountIdentifiers.localAccountId || accountIdentifiers.username)) { | ||
const matchedAccounts = allAccounts.filter(accountObj => { | ||
if (accountIdentifiers.username && accountIdentifiers.username.toLowerCase() !== accountObj.username.toLowerCase()) { | ||
return false; | ||
} | ||
if (accountIdentifiers.homeAccountId && accountIdentifiers.homeAccountId.toLowerCase() !== accountObj.homeAccountId.toLowerCase()) { | ||
return false; | ||
} | ||
if (accountIdentifiers.localAccountId && accountIdentifiers.localAccountId.toLowerCase() !== accountObj.localAccountId.toLowerCase()) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return matchedAccounts[0] || null; | ||
} else { | ||
return null; | ||
} | ||
} | ||
/** | ||
* Given 1 or more accountIdentifiers, returns the Account object if the user is signed-in | ||
* @param accountIdentifiers | ||
*/ | ||
function useAccount(accountIdentifiers) { | ||
const { | ||
instance, | ||
inProgress | ||
} = useMsal(); | ||
const initialStateValue = inProgress === InteractionStatus.Startup ? null : getAccount(instance, accountIdentifiers); | ||
const [account, setAccount] = useState(initialStateValue); | ||
useEffect(() => { | ||
const currentAccount = getAccount(instance, accountIdentifiers); | ||
if (!AccountEntity.accountInfoIsEqual(account, currentAccount, true)) { | ||
setAccount(currentAccount); | ||
} | ||
}, [inProgress, accountIdentifiers, instance, account]); | ||
return account; | ||
} | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
function isAuthenticated(allAccounts, account, matchAccount) { | ||
function isAuthenticated(allAccounts, matchAccount) { | ||
if (matchAccount && (matchAccount.username || matchAccount.homeAccountId || matchAccount.localAccountId)) { | ||
return !!account; | ||
return !!getAccountByIdentifiers(allAccounts, matchAccount); | ||
} | ||
@@ -243,11 +213,8 @@ | ||
const { | ||
accounts: allAccounts, | ||
inProgress | ||
accounts: allAccounts | ||
} = useMsal(); | ||
const account = useAccount(matchAccount || {}); | ||
const initialStateValue = inProgress === InteractionStatus.Startup ? false : isAuthenticated(allAccounts, account, matchAccount); | ||
const [hasAuthenticated, setHasAuthenticated] = useState(initialStateValue); | ||
const [hasAuthenticated, setHasAuthenticated] = useState(() => isAuthenticated(allAccounts, matchAccount)); | ||
useEffect(() => { | ||
setHasAuthenticated(isAuthenticated(allAccounts, account, matchAccount)); | ||
}, [allAccounts, account, matchAccount]); | ||
setHasAuthenticated(isAuthenticated(allAccounts, matchAccount)); | ||
}, [allAccounts, matchAccount]); | ||
return hasAuthenticated; | ||
@@ -324,5 +291,78 @@ } | ||
*/ | ||
function getAccount(instance, accountIdentifiers) { | ||
if (!accountIdentifiers || !accountIdentifiers.homeAccountId && !accountIdentifiers.localAccountId && !accountIdentifiers.username) { | ||
// If no account identifiers are provided, return active account | ||
return instance.getActiveAccount(); | ||
} | ||
return getAccountByIdentifiers(instance.getAllAccounts(), accountIdentifiers); | ||
} | ||
/** | ||
* Invokes a login call if a user is not currently signed-in. Failed logins can be retried using the login callback returned. | ||
* Optionally provide a request object to be used in the login call. | ||
* Given 1 or more accountIdentifiers, returns the Account object if the user is signed-in | ||
* @param accountIdentifiers | ||
*/ | ||
function useAccount(accountIdentifiers) { | ||
const { | ||
instance, | ||
inProgress, | ||
logger | ||
} = useMsal(); | ||
const [account, setAccount] = useState(() => getAccount(instance, accountIdentifiers)); | ||
useEffect(() => { | ||
setAccount(currentAccount => { | ||
const nextAccount = getAccount(instance, accountIdentifiers); | ||
if (!AccountEntity.accountInfoIsEqual(currentAccount, nextAccount, true)) { | ||
logger.info("useAccount - Updating account"); | ||
return nextAccount; | ||
} | ||
return currentAccount; | ||
}); | ||
}, [inProgress, accountIdentifiers, instance, logger]); | ||
return account; | ||
} | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
const ReactAuthErrorMessage = { | ||
invalidInteractionType: { | ||
code: "invalid_interaction_type", | ||
desc: "The provided interaction type is invalid." | ||
}, | ||
unableToFallbackToInteraction: { | ||
code: "unable_to_fallback_to_interaction", | ||
desc: "Interaction is required but another interaction is already in progress. Please try again when the current interaction is complete." | ||
} | ||
}; | ||
class ReactAuthError extends AuthError { | ||
constructor(errorCode, errorMessage) { | ||
super(errorCode, errorMessage); | ||
Object.setPrototypeOf(this, ReactAuthError.prototype); | ||
this.name = "ReactAuthError"; | ||
} | ||
static createInvalidInteractionTypeError() { | ||
return new ReactAuthError(ReactAuthErrorMessage.invalidInteractionType.code, ReactAuthErrorMessage.invalidInteractionType.desc); | ||
} | ||
static createUnableToFallbackToInteractionError() { | ||
return new ReactAuthError(ReactAuthErrorMessage.unableToFallbackToInteraction.code, ReactAuthErrorMessage.unableToFallbackToInteraction.desc); | ||
} | ||
} | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
/** | ||
* If a user is not currently signed in this hook invokes a login. Failed logins can be retried using the login callback returned. | ||
* If a user is currently signed in this hook attempts to acquire a token. Subsequent token requests can use the acquireToken callback returned. | ||
* Optionally provide a request object to be used in the login/acquireToken call. | ||
* Optionally provide a specific user that should be logged in. | ||
@@ -341,4 +381,24 @@ * @param interactionType | ||
const isAuthenticated = useIsAuthenticated(accountIdentifiers); | ||
const [[result, error], setResponse] = useState([null, null]); | ||
const [hasBeenCalled, setHasBeenCalled] = useState(false); | ||
const account = useAccount(accountIdentifiers); | ||
const [[result, error], setResponse] = useState([null, null]); // Boolean used to check if interaction is in progress in acquireTokenSilent fallback. Use Ref instead of state to prevent acquireToken function from being regenerated on each change to interactionInProgress value | ||
const interactionInProgress = useRef(inProgress !== InteractionStatus.None); | ||
useEffect(() => { | ||
interactionInProgress.current = inProgress !== InteractionStatus.None; | ||
}, [inProgress]); // Flag used to control when the hook calls login/acquireToken | ||
const shouldAcquireToken = useRef(true); | ||
useEffect(() => { | ||
if (!!error) { | ||
// Errors should be handled by consuming component | ||
shouldAcquireToken.current = false; | ||
return; | ||
} | ||
if (!!result) { | ||
// Token has already been acquired, consuming component/application is responsible for renewing | ||
shouldAcquireToken.current = false; | ||
return; | ||
} | ||
}, [error, result]); | ||
const login = useCallback(async (callbackInteractionType, callbackRequest) => { | ||
@@ -363,5 +423,55 @@ const loginType = callbackInteractionType || interactionType; | ||
default: | ||
throw "Invalid interaction type provided."; | ||
throw ReactAuthError.createInvalidInteractionTypeError(); | ||
} | ||
}, [instance, interactionType, authenticationRequest, logger]); | ||
const acquireToken = useCallback(async (callbackInteractionType, callbackRequest) => { | ||
const fallbackInteractionType = callbackInteractionType || interactionType; | ||
let tokenRequest; | ||
if (callbackRequest) { | ||
logger.trace("useMsalAuthentication - acquireToken - Using request provided in the callback"); | ||
tokenRequest = { ...callbackRequest | ||
}; | ||
} else if (authenticationRequest) { | ||
logger.trace("useMsalAuthentication - acquireToken - Using request provided in the hook"); | ||
tokenRequest = { ...authenticationRequest, | ||
scopes: authenticationRequest.scopes || OIDC_DEFAULT_SCOPES | ||
}; | ||
} else { | ||
logger.trace("useMsalAuthentication - acquireToken - No request object provided, using default request."); | ||
tokenRequest = { | ||
scopes: OIDC_DEFAULT_SCOPES | ||
}; | ||
} | ||
if (!tokenRequest.account && account) { | ||
logger.trace("useMsalAuthentication - acquireToken - Attaching account to request"); | ||
tokenRequest.account = account; | ||
} | ||
const getToken = async () => { | ||
logger.verbose("useMsalAuthentication - Calling acquireTokenSilent"); | ||
return instance.acquireTokenSilent(tokenRequest).catch(async e => { | ||
if (e instanceof InteractionRequiredAuthError) { | ||
if (!interactionInProgress.current) { | ||
logger.error("useMsalAuthentication - Interaction required, falling back to interaction"); | ||
return login(fallbackInteractionType, tokenRequest); | ||
} else { | ||
logger.error("useMsalAuthentication - Interaction required but is already in progress. Please try again, if needed, after interaction completes."); | ||
throw ReactAuthError.createUnableToFallbackToInteractionError(); | ||
} | ||
} | ||
throw e; | ||
}); | ||
}; | ||
return getToken().then(response => { | ||
setResponse([response, null]); | ||
return response; | ||
}).catch(e => { | ||
setResponse([null, e]); | ||
throw e; | ||
}); | ||
}, [instance, interactionType, authenticationRequest, logger, account, login]); | ||
useEffect(() => { | ||
@@ -396,14 +506,23 @@ const callbackId = instance.addEventCallback(message => { | ||
useEffect(() => { | ||
if (!hasBeenCalled && !error && !isAuthenticated && inProgress === InteractionStatus.None) { | ||
logger.info("useMsalAuthentication - No user is authenticated, attempting to login"); // Ensure login is only called one time from within this hook, any subsequent login attempts should use the callback returned | ||
if (shouldAcquireToken.current && inProgress === InteractionStatus.None) { | ||
shouldAcquireToken.current = false; | ||
setHasBeenCalled(true); | ||
login().catch(() => { | ||
// Errors are handled by the event handler above | ||
return; | ||
}); | ||
if (!isAuthenticated) { | ||
logger.info("useMsalAuthentication - No user is authenticated, attempting to login"); | ||
login().catch(() => { | ||
// Errors are saved in state above | ||
return; | ||
}); | ||
} else if (account) { | ||
logger.info("useMsalAuthentication - User is authenticated, attempting to acquire token"); | ||
acquireToken().catch(() => { | ||
// Errors are saved in state above | ||
return; | ||
}); | ||
} | ||
} | ||
}, [isAuthenticated, inProgress, error, hasBeenCalled, login, logger]); | ||
}, [isAuthenticated, account, inProgress, login, acquireToken, logger]); | ||
return { | ||
login, | ||
acquireToken, | ||
result, | ||
@@ -410,0 +529,0 @@ error |
export declare const name = "@azure/msal-react"; | ||
export declare const version = "1.2.0"; | ||
export declare const version = "1.3.0"; |
/// <reference types="react" /> | ||
import { AccountIdentifiers } from "../types/AccountIdentifiers"; | ||
import { AccountInfo } from "@azure/msal-browser"; | ||
declare type FaaCFunction = <T>(args: T) => React.ReactNode; | ||
@@ -15,2 +16,3 @@ export declare function getChildrenOrFunction<T>(children: React.ReactNode | FaaCFunction, args: T): React.ReactNode; | ||
export declare function accountArraysAreEqual(arrayA: Array<AccountIdentifiers>, arrayB: Array<AccountIdentifiers>): boolean; | ||
export declare function getAccountByIdentifiers(allAccounts: AccountInfo[], accountIdentifiers: AccountIdentifiers): AccountInfo | null; | ||
export {}; |
{ | ||
"name": "@azure/msal-react", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"author": { | ||
@@ -44,3 +44,3 @@ "name": "Microsoft", | ||
"peerDependencies": { | ||
"@azure/msal-browser": "^2.21.0", | ||
"@azure/msal-browser": "^2.22.0", | ||
"react": "^16.8.0 || ^17" | ||
@@ -50,3 +50,3 @@ }, | ||
"devDependencies": { | ||
"@azure/msal-browser": "^2.21.0", | ||
"@azure/msal-browser": "^2.22.0", | ||
"@testing-library/jest-dom": "^5.11.5", | ||
@@ -53,0 +53,0 @@ "@testing-library/react": "^11.2.3", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
226155
27
1941