@machinemetrics/mm-react-embeddable 2.0.0-0 → 2.0.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.
- package/README.md +13 -0
- package/dist/mm-react-embeddable.cjs.js +8 -8
- package/dist/mm-react-embeddable.es.js +461 -421
- package/dist/mm-react-embeddable.umd.js +9 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
# mm-react-embeddable
|
|
2
2
|
|
|
3
3
|
Components and hooks for embedding mm-apps into an interface. Primarily used for sandbox testing the creation of custom dashboard widgets.
|
|
4
|
+
|
|
5
|
+
## Consent UI
|
|
6
|
+
|
|
7
|
+
`useEmbeddedAuthorizations()` exposes the pending entries and the actions on them
|
|
8
|
+
(`authorize` for the popup path, `approve`/`dismiss` for brokered entries). Every
|
|
9
|
+
first-party host that passes a `credentialBroker` to `MMEmbeddableZoneProvider`
|
|
10
|
+
renders its own consent UI over that hook (converge, OperatorView and operator-kiosk
|
|
11
|
+
each have one).
|
|
12
|
+
|
|
13
|
+
`EmbeddedAuthorizationPrompt` is the default **popup-only** prompt for hosts that
|
|
14
|
+
cannot broker, such as an http development host (the mmdev playground). It renders
|
|
15
|
+
legacy entries with an Authorize button that opens the OAuth popup, ignores brokered
|
|
16
|
+
entries, and logs a one-time error in that case so the missing host UI is noticed.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react"),he=require("prop-types"),ge=require("styled-components"),Q=r=>r&&r.__esModule?r:{default:r},E=Q(t),T=Q(he),W=Q(ge),H=t.createContext(null),K=t.createContext({pending:[],authorize:()=>{},approve:()=>{},complete:()=>{},dismiss:()=>{},originEpochs:{},credentials:{},unsupported:{}}),se="access_token",J=r=>r?r.kind?r:r.accessToken?{kind:se,accessToken:r.accessToken,expiresAt:r.expiresAt||null}:null:null,Ee=new Set(["host_not_allowed","invalid_token","provider_unavailable"]),N=typeof window!="undefined"&&window.location.protocol==="https:",ke=8e3,G=r=>{try{return new URL(r).origin}catch{return null}},ee=(r,o)=>{const c=o==null?void 0:o.origin;return!c||G(r==null?void 0:r.authUrl)!==c?null:c},te=r=>{if(!r||/^\d{1,3}(?:\.\d{1,3}){3}$/.test(r)||r.includes(":"))return r;const o=r.split(".");return o.length<=2?r:o.slice(-2).join(".")},oe=(r,o)=>{try{const c=new URL(r),l=new URL(o);return c.protocol!==l.protocol?!0:te(c.hostname)!==te(l.hostname)}catch{return!0}},ue=(r,{onBlocked:o,onClosed:c})=>{const l=window.open(r.authUrl,"mm-embed-auth","popup,width=600,height=700");if(!l)return o(),()=>{};const b=setInterval(()=>{l.closed&&(clearInterval(b),c())},1e3);return()=>clearInterval(b)},ie=({children:r,launcher:o,broker:c})=>{const l=t.useContext(H),[b,m]=t.useState({}),[A,C]=t.useState({}),[z,f]=t.useState({}),[w,I]=t.useState({}),L=t.useRef(z);L.current=z;const g=t.useRef(b);g.current=b;const U=t.useRef(null),Z=t.useRef({}),S=t.useRef({}),F=t.useRef({}),$=t.useRef(c);$.current=c;const y=t.useCallback(e=>{const n=S.current[e];if(!n)return;const s=i=>!i[e]||i[e].clientId?i:{...i,[e]:{...i[e],clientId:n}};m(s),I(s)},[]),a=t.useCallback((e,n)=>{e&&(I(s=>s[e]?s:{...s,[e]:{origin:e,reason:n,clientId:S.current[e]||null}}),y(e))},[y]),d=t.useCallback((e,{v2:n=!1,origin:s=null}={})=>{s&&(n&&L.current[s]||(m(i=>i[s]?i:{...i,[s]:{origin:s,authUrl:e.authUrl,silentUrl:e.silentUrl||null,status:n&&$.current?"broker":n&&e.silentUrl&&N?"silent":"interactive",v2:n,scope:e.scope||null,acceptsCredentials:F.current[s]||null,consent:null,error:null,clientId:e.clientId||S.current[s]||null,popupBlocked:!1}}),y(s)))},[y]),u=t.useCallback(e=>{m(n=>{if(!n[e])return n;const s={...n};return delete s[e],s})},[]),p=t.useCallback(e=>{e&&(u(e),C(n=>({...n,[e]:(n[e]||0)+1})))},[u]),R=t.useCallback(e=>{m(n=>!n[e]||n[e].status==="interactive"?n:{...n,[e]:{...n[e],status:"interactive"}})},[]),x=t.useCallback(e=>{u(e.origin)},[u]),M=t.useCallback((e,n)=>{const s=ee(e,n);if(s){if(oe(s,window.location.origin)){a(s,"cross-site-legacy");return}d(e,{origin:s})}},[d,a]);t.useEffect(()=>{if(!l)return;const e=[l.addListener("*","init",(n,s)=>{s!=null&&s.origin&&(n!=null&&n.clientId)&&(S.current[s.origin]=n.clientId,y(s.origin)),s!=null&&s.origin&&Array.isArray(n==null?void 0:n.acceptsCredentials)&&(F.current[s.origin]=n.acceptsCredentials)}),l.addListener("*","authorize",M),l.addListener("*","login",M),l.addListener("*","credential-request",(n,s)=>d(n,{v2:!0,origin:ee(n,s)}))];return()=>e.forEach(n=>n())},[l,M,d,y]),t.useEffect(()=>{const e=n=>{const s=n==null?void 0:n.data;if(!s||s.type!=="authorized"&&s.type!=="authorize-failed")return;const i=G(s.authUrl);if(!(!i||n.origin!==i)){if(s.type==="authorized"){if(s.accessToken){if(!g.current[i])return;f(h=>({...h,[i]:J({accessToken:s.accessToken})})),u(i)}else p(i);return}R(i)}};return window.addEventListener("message",e),()=>window.removeEventListener("message",e)},[u,p,R]);const _=t.useCallback((e,n)=>{const s=e==null?void 0:e.origin,i=J(n);!s||!i||(f(h=>({...h,[s]:i})),u(s))},[u]),k=t.useCallback((e,n)=>{m(s=>s[e]?{...s,[e]:{...s[e],...n}}:s)},[]),O=t.useCallback(async(e,{approve:n}={})=>{const s=$.current,{origin:i}=e;let h;try{h=await s({origin:i,clientId:e.clientId,scope:e.scope,authUrl:e.authUrl},{approve:n})}catch{n?k(i,{error:{reason:"broker_unavailable"}}):k(i,{status:e.silentUrl&&N?"silent":"interactive"});return}if((h==null?void 0:h.status)===200&&h.credential){_(e,h.credential);return}if((h==null?void 0:h.status)===409&&h.consentRequired){k(i,{status:"interactive",consent:{client:h.client||null,stepUp:!!h.stepUp,scope:h.scope||e.scope},error:null});return}const V=(h==null?void 0:h.error)||"broker_error";if(!n&&Ee.has(V)){k(i,{status:e.silentUrl&&N?"silent":"interactive"});return}if(n&&V==="consent_denied"){k(i,{error:null});return}u(i),I(Y=>({...Y,[i]:{origin:i,reason:`broker:${V}`,detail:h||null,clientId:e.clientId||S.current[i]||null}}))},[_,k,u]),v=t.useRef({});t.useEffect(()=>{Object.values(b).forEach(e=>{e.status!=="broker"||v.current[e.origin]||(v.current[e.origin]=!0,O(e).finally(()=>{delete v.current[e.origin]}))})},[b,O]);const P=t.useCallback(e=>!$.current||!(e!=null&&e.consent)?Promise.resolve():O(e,{approve:!0}),[O]);t.useEffect(()=>{const e=Z.current;Object.values(b).forEach(n=>{n.status==="silent"&&!e[n.origin]&&(e[n.origin]=setTimeout(()=>{delete e[n.origin],R(n.origin)},ke))}),Object.keys(e).forEach(n=>{(!b[n]||b[n].status!=="silent")&&(clearTimeout(e[n]),delete e[n])})},[b,R]),t.useEffect(()=>()=>{U.current&&U.current(),Object.values(Z.current).forEach(clearTimeout)},[]);const q=t.useCallback(e=>{U.current&&(U.current(),U.current=null);const n=o||ue;U.current=n(e,{onBlocked:()=>{m(s=>s[e.origin]?{...s,[e.origin]:{...s[e.origin],popupBlocked:!0}}:s)},onClosed:()=>{if(e.v2){N&&m(s=>!s[e.origin]||!s[e.origin].silentUrl?s:{...s,[e.origin]:{...s[e.origin],status:"silent"}});return}p(e.origin)}})},[o,p]),j={pending:Object.values(b).filter(e=>e.status==="interactive"),authorize:q,approve:P,complete:_,dismiss:x,originEpochs:A,credentials:z,unsupported:w},B=Object.values(b).filter(e=>e.status==="silent"&&e.silentUrl);return E.default.createElement(K.Provider,{value:j},r,B.map(e=>E.default.createElement("iframe",{key:`mm-silent-auth-${e.origin}`,src:e.silentUrl,title:`MachineMetrics silent authorization (${e.origin})`,"aria-hidden":"true",tabIndex:-1,style:{display:"none"}})))};ie.propTypes={children:T.default.node,launcher:T.default.func,broker:T.default.func};const ce=()=>t.useContext(K),ae=r=>{const{originEpochs:o}=t.useContext(K),c=G(r);return c&&o[c]||0},le=r=>{const{credentials:o}=t.useContext(K),c=G(r);return c&&o[c]||null},de=r=>{const{unsupported:o}=t.useContext(K),c=G(r);return c&&o[c]||null},fe=({children:r,authorizationLauncher:o,credentialBroker:c})=>{const[l,b]=t.useState({}),[m,A]=t.useState({}),C=t.useRef({}),z=t.useRef({}),f=t.useRef({}),w=t.useCallback((a,d)=>{var u;return!!((u=f.current[a])!=null&&u.has(d))},[]),I=t.useCallback((a,d,u)=>{b(p=>({...p,[a]:{origin:d,frameRef:u}}))},[]),L=t.useCallback(a=>{b(d=>{const u={...d};return delete u[a],u}),A(d=>{const u={...d};return delete u[a],u}),delete f.current[a]},[]);t.useEffect(()=>{const a=(u=[],p,R)=>{u.forEach(x=>x(p,R))},d=u=>{var O,v,P,q,j,B,e;if(!((O=u==null?void 0:u.data)!=null&&O.instanceId))return;const{instanceId:p,type:R}=u.data,x=l[p],M=(P=(v=x==null?void 0:x.frameRef)==null?void 0:v.current)==null?void 0:P.contentWindow;if(!M||u.source!==M||u.origin!==x.origin)return;const _={origin:u.origin},k=C.current;if(R==="response"){const{callbackId:n,response:s,error:i}=u.data,h=z.current[n];h&&(h(s,i),delete z.current[n]);return}if(R==="mark-registered"){const{requestType:n}=u.data;f.current[p]||(f.current[p]=new Set),f.current[p].add(n);return}R==="ready"&&A(n=>({...n,[p]:!0})),a((q=k[p])==null?void 0:q[R],u.data,_),a((j=k["*"])==null?void 0:j[R],u.data,_),a((B=k[p])==null?void 0:B["*"],u.data,_),a((e=k["*"])==null?void 0:e["*"],u.data,_)};return window.addEventListener("message",d),()=>{window.removeEventListener("message",d)}},[l]);const g=t.useCallback((a,d,u)=>{var R,x;const p=l[a];if(!p){console.warn(`Origin not set for id: ${a}. Unable to send message.`);return}(x=(R=p==null?void 0:p.frameRef)==null?void 0:R.current)!=null&&x.contentWindow&&p.frameRef.current.contentWindow.postMessage({type:d,...u,instanceId:a},p.origin)},[l]),U=t.useRef(0),Z=t.useCallback(()=>`${Date.now()}-${++U.current}`,[]),S=t.useCallback(async(a,d,u={})=>new Promise((p,R)=>{const x=Z(),M=(_,k)=>{delete z.current[x],k?R(k):p(_)};z.current[x]=M,g(a,"request",{callbackId:x,requestType:d,data:u})}),[g]),F=t.useCallback((a,d)=>{g(a,"load-options",d)},[g]),$=t.useCallback((a,d)=>{g(a,"validate",d)},[g]),y=t.useMemo(()=>({registerFrame:I,unregisterFrame:L,sendMessage:g,sendRequest:S,loadOptions:F,validate:$,hasHandler:w,readyFrames:m,addListener:(a,d,u)=>(C.current[a]||(C.current[a]={}),C.current[a][d]||(C.current[a][d]=[]),C.current[a][d].push(u),()=>{C.current[a][d]=C.current[a][d].filter(p=>p!==u)})}),[I,L,g,S,F,$,w,m]);return E.default.createElement(H.Provider,{value:y},E.default.createElement(ie,{launcher:o,broker:c},r))},D=(r,o,c)=>{const l=t.useContext(H);if(l===null)throw new Error("useEmbeddedEffect must be used within a MMEmbeddableZoneProvider");const b=t.useRef(c);t.useEffect(()=>{b.current=c},[c]),t.useEffect(()=>{if(!(!r||!o)){if(o.startsWith("key:")){const m=o.substring(4);return l.readyFrames[r]&&l.sendMessage(r,"subscribe-key",{keyChord:m}),l.addListener(r,`key-pressed:${m}`,(...C)=>b.current(...C))}return l.addListener(r,o,(...m)=>b.current(...m))}},[l,r,o])};fe.propTypes={children:T.default.node.isRequired,authorizationLauncher:T.default.func,credentialBroker:T.default.func};const X=W.default.default||W.default,Re=1e4,Ce="credentials",we=(r,o)=>`${r}/mm-embedded-interop?id=${encodeURIComponent(o)}&origin=${encodeURIComponent(window.location.origin)}&caps=${Ce}`,xe=X.iframe`
|
|
2
2
|
width: 100%;
|
|
3
3
|
height: 100%;
|
|
4
4
|
border: none;
|
|
5
|
-
visibility: ${
|
|
6
|
-
`,
|
|
5
|
+
visibility: ${r=>r.$isReady?"visible":"hidden"};
|
|
6
|
+
`,Te=X.div`
|
|
7
7
|
width: 100%;
|
|
8
8
|
height: 100%;
|
|
9
9
|
display: flex;
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
text-align: center;
|
|
17
17
|
color: #555;
|
|
18
18
|
font-size: 14px;
|
|
19
|
-
`,
|
|
19
|
+
`,pe=r=>{const o=r&&r.origin||"This app",c=r&&r.reason||"";switch(c){case"broker:scope_above_ceiling":return{title:`${o} can’t be authorized here`,detail:"It asks for permissions your role doesn’t have."};case"broker:origin_mismatch":case"broker:unknown_client":return{title:`${o} can’t be authorized here`,detail:"It isn’t registered to run at this address."};case"broker:cross_company_principal":return{title:`${o} can’t be authorized here`,detail:"It can’t be authorized for this account."};default:return c.startsWith("broker:")?{title:`${o} can’t be authorized here`,detail:"Authorization was refused."}:{title:`${o} can’t be embedded here`,detail:"It needs an update to support embedding from a different domain."}}},be=({entry:r})=>{const o=pe(r);return E.default.createElement(Te,{"data-reason":r&&r.reason||void 0},E.default.createElement("div",{style:{fontWeight:600}},o.title),E.default.createElement("div",{style:{fontSize:12,color:"#888"}},o.detail))};be.propTypes={entry:T.default.shape({origin:T.default.string,name:T.default.string,reason:T.default.string,detail:T.default.object})};const ze=X.div`
|
|
20
20
|
height: 100%;
|
|
21
21
|
display: flex;
|
|
22
22
|
flex: 1;
|
|
23
23
|
align-items: center;
|
|
24
24
|
justify-content: center;
|
|
25
|
-
`,
|
|
25
|
+
`,Ie=()=>E.default.createElement(ze,null,E.default.createElement("div",{style:{textAlign:"center"}},"This app couldn’t be loaded as an embeddable widget.")),me=({id:r,src:o,path:c,onInit:l=()=>{},onTimeout:b,context:m={},params:A={},renderUnsupported:C,renderTimeout:z,...f})=>{if("isConfiguring"in f)return console.error("isConfiguring must be passed via the params prop to MMEmbeddableZone"),null;if("colorMode"in m)return console.error("colorMode must be passed via the params prop to MMEmbeddableZone"),null;if("isFullScreen"in m)return console.error("isFullScreen must be passed via the params prop to MMEmbeddableZone"),null;const w=t.useRef(),[I,L]=t.useState(),[g,U]=t.useState(0),[Z,S]=t.useState(!1),[F,$]=t.useState(!1),y=ae(o),a=le(o),d=de(o),[u,p]=t.useState(0),[R,x]=t.useState(null),{registerFrame:M,unregisterFrame:_,sendMessage:k}=t.useContext(H),[O,v]=t.useState(),P=t.useRef(),q=t.useRef(b),j=t.useRef(l),B=t.useRef(!1),e=t.useCallback(()=>{B.current=!1,clearTimeout(P.current),U(0),S(!1),$(!1),x(null)},[]);t.useEffect(()=>{(y>0||u>0)&&e()},[y,u,e]),t.useEffect(()=>{q.current=b},[b]),t.useEffect(()=>{j.current=l},[l]),t.useEffect(()=>{g&&k(r,"set-context",{context:{...m}})},[m,r,g]),t.useEffect(()=>{g&&k(r,"set-params",{params:A})},[A,r,g]),t.useEffect(()=>{c!==void 0&&v((c==null?void 0:c.indexOf("/"))===0?c:`/${c}`)},[c]),t.useEffect(()=>{O===void 0||!g||k(r,"navigate",{path:O})},[O,g,r,k]),t.useEffect(()=>{if(!o||!r||!w)return;e();const i=new URL(o);return M(r,i.origin,w),j.current(),L(we(i.origin,r)),()=>{_(r)}},[o,r,M,w,e]),t.useEffect(()=>{if(!(!I||B.current))return P.current=setTimeout(()=>{S(!0),q.current&&q.current()},Re),()=>clearTimeout(P.current)},[I,o,y,u]);const n=t.useCallback(()=>{B.current=!0,clearTimeout(P.current),S(!1)},[]);D(r,"init",i=>{n(),Array.isArray(i==null?void 0:i.acceptsCredentials)&&x(i.acceptsCredentials)}),D(r,"authorize",n),D(r,"login",n),D(r,"credential-request",()=>{n(),$(!0)}),t.useEffect(()=>{if(!F||!a)return;const i=R;if(Array.isArray(i)&&!i.includes(a.kind))return;const h=a.kind==="access_token"?{accessToken:a.accessToken}:{};k(r,"credential",{...h,credential:a})},[F,a,R,r,k]),D(r,"ready",()=>{n(),U(i=>i+1)});const s=t.useRef();return D(r,"heartbeat",()=>{s.current&&clearTimeout(s.current),s.current=setTimeout(()=>{p(i=>i+1)},6e3)}),t.useEffect(()=>()=>{s.current&&clearTimeout(s.current)},[]),d?C?C(d):E.default.createElement(be,{entry:d}):Z&&!g?z?z():E.default.createElement(Ie,null):E.default.createElement(xe,{key:`auth-epoch-${y}-frame-${u}`,$isReady:g,ref:w,src:I,...f})};me.propTypes={id:T.default.string,src:T.default.string.isRequired,path:T.default.string,onInit:T.default.func,onTimeout:T.default.func,renderUnsupported:T.default.func,renderTimeout:T.default.func};const ye=W.default.div`
|
|
26
26
|
position: fixed;
|
|
27
27
|
inset: 0;
|
|
28
28
|
background: rgba(0, 0, 0, 0.45);
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
align-items: center;
|
|
31
31
|
justify-content: center;
|
|
32
32
|
z-index: 10000;
|
|
33
|
-
`,
|
|
33
|
+
`,Se=W.default.div`
|
|
34
34
|
background: #fff;
|
|
35
35
|
border-radius: 8px;
|
|
36
36
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
justify-content: space-between;
|
|
46
46
|
padding: 10px 0;
|
|
47
47
|
gap: 16px;
|
|
48
|
-
`,
|
|
48
|
+
`,Ae=W.default.button`
|
|
49
49
|
background: #16a500;
|
|
50
50
|
color: #fff;
|
|
51
51
|
border: none;
|
|
@@ -58,4 +58,4 @@
|
|
|
58
58
|
color: #b3261e;
|
|
59
59
|
font-size: 13px;
|
|
60
60
|
margin: 4px 0 0;
|
|
61
|
-
`,
|
|
61
|
+
`,re='button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';let ne=!1;const Me=()=>{const{pending:r,authorize:o,dismiss:c}=ce(),l=r.filter(f=>!f.consent),b=r.length-l.length,m=t.useRef(null),A=t.useRef(null),C=l.length>0;if(t.useEffect(()=>{b>0&&!ne&&(ne=!0,console.error("[mm-react-embeddable] EmbeddedAuthorizationPrompt received a brokered consent entry. A host with a credentialBroker must render its own consent UI (useEmbeddedAuthorizations: approve/dismiss); this default prompt only handles the popup path."))},[b]),t.useEffect(()=>{if(!C||!m.current)return;A.current=document.activeElement;const f=m.current.querySelector(re);return f&&f.focus(),()=>{const w=A.current;w&&typeof w.focus=="function"&&w.focus()}},[C]),!C)return null;const z=f=>{if(f.key==="Escape"){f.preventDefault(),l.forEach(g=>c(g));return}if(f.key!=="Tab"||!m.current)return;const w=Array.from(m.current.querySelectorAll(re));if(w.length===0)return;const I=w[0],L=w[w.length-1];f.shiftKey&&document.activeElement===I?(f.preventDefault(),L.focus()):!f.shiftKey&&document.activeElement===L&&(f.preventDefault(),I.focus())};return E.default.createElement(ye,null,E.default.createElement(Se,{ref:m,role:"dialog","aria-modal":"true","aria-labelledby":"mm-embedded-authorization-title",onKeyDown:z},E.default.createElement("h3",{id:"mm-embedded-authorization-title",style:{margin:"0 0 8px"}},"Authorization Required"),E.default.createElement("p",{style:{margin:"0 0 12px",fontSize:14,color:"#555"}},"The following apps need you to sign in and authorize them before they can load."),l.map(f=>E.default.createElement("div",{key:f.origin},E.default.createElement(_e,null,E.default.createElement("div",null,E.default.createElement("div",{style:{fontWeight:600}},f.origin)),E.default.createElement(Ae,{onClick:()=>o(f)},"Authorize")),f.popupBlocked&&E.default.createElement(Ue,null,"The sign-in window was blocked. Allow popups for this site, then try again.")))))};exports.CREDENTIAL_KIND_ACCESS_TOKEN=se;exports.EmbeddedAuthorizationContext=K;exports.EmbeddedAuthorizationPrompt=Me;exports.MMEmbeddableZone=me;exports.MMEmbeddableZoneContext=H;exports.MMEmbeddableZoneProvider=fe;exports.isCrossSite=oe;exports.normalizeCredential=J;exports.popupAuthorizationLauncher=ue;exports.unsupportedCopy=pe;exports.useEmbeddedAuthorizations=ce;exports.useEmbeddedCredential=le;exports.useEmbeddedEffect=D;exports.useEmbeddedOriginEpoch=ae;exports.useEmbeddedUnsupported=de;
|