@fntio/infra-client-plugin-example 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ "use strict";(()=>{var $=["user","admin"];var G=/^\d{12}$/;var q=["--background","--surface","--surface-foreground","--muted","--muted-foreground","--text-secondary","--placeholder","--border","--border-strong","--ring","--card-bg","--popover-bg","--state-hover","--state-selected","--gray-1","--gray-2","--gray-3","--gray-4","--gray-5","--gray-6","--gray-7","--gray-8","--gray-9","--gray-10","--gray-11","--gray-12","--accent-1","--accent-2","--accent-3","--accent-4","--accent-5","--accent-6","--accent-7","--accent-8","--accent-9","--accent-10","--accent-11","--accent-12","--brand-primary","--brand-primary-hover","--brand-foreground","--brand-text","--brand-muted-foreground","--success","--success-muted","--warning","--warning-muted","--danger","--danger-hover","--danger-muted","--radius","--radius-md","--radius-lg","--radius-xl","--font-sans","--font-mono","--ease-standard","--ease-emphasized"],se=new Set(q);function ae(e){return se.has(e)}function M(e){if(!e||typeof e!="object")return!1;let n=e;if(n.appearance!=="light"&&n.appearance!=="dark"||!n.tokens||typeof n.tokens!="object"||Array.isArray(n.tokens))return!1;for(let[r,s]of Object.entries(n.tokens))if(!ae(r)||typeof s!="string")return!1;let t=n.branding;return!(!t||typeof t!="object"||typeof t.projectName!="string"||t.logoUrl!==void 0&&typeof t.logoUrl!="string")}function le(e){return e.length>0&&e.length<=200&&!/[;{}<>]/.test(e)&&!/url\s*\(|expression\s*\(|@import/i.test(e)}function z(e,n=":root"){let t=[`color-scheme: ${e.appearance};`];for(let r of q){let s=e.tokens[r];s!==void 0&&le(s)&&t.push(`${r}: ${s};`)}return`${n} {
2
+ ${t.join(`
3
+ `)}
4
+ }
5
+ `}var A="infra-client-plugin",v=2,V=1e4,de=["init","sessionChanged","sessionEnded","accessChanged","themeChanged"],ue=["ready","requestSessionRefresh","requestAccessRefresh"];function w(e,n,t,r){return{protocol:A,version:v,type:e,instanceId:n,...r===void 0?{}:{requestId:r},payload:t}}var f=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),b=e=>typeof e=="string"&&e.length>0;function j(e){return f(e)&&b(e.token)&&typeof e.expiresAt=="number"&&Number.isFinite(e.expiresAt)}function ce(e){return f(e)&&typeof e.accountNumber=="string"&&G.test(e.accountNumber)&&typeof e.displayName=="string"&&(e.regions===void 0||Array.isArray(e.regions)&&e.regions.every(n=>typeof n=="string"))&&Array.isArray(e.permissions)&&e.permissions.every(n=>$.includes(n))}function D(e){return Array.isArray(e)&&e.every(ce)}function ge(e){return f(e)&&b(e.subject)&&(e.principalType==="user"||e.principalType==="service")&&(e.email===void 0||typeof e.email=="string")&&(e.displayName===void 0||typeof e.displayName=="string")}function pe(e){return f(e)&&b(e.id)&&typeof e.displayName=="string"&&typeof e.version=="string"&&typeof e.revision=="string"&&typeof e.apiBaseUrl=="string"}var F={init:e=>f(e)&&j(e.session)&&ge(e.user)&&pe(e.plugin)&&(e.entry==="user"||e.entry==="admin")&&D(e.accounts)&&M(e.theme),sessionChanged:j,sessionEnded:e=>f(e)&&(e.reason==="signedOut"||e.reason==="expired"||e.reason==="unloaded"),accessChanged:e=>f(e)&&D(e.accounts)&&typeof e.entryAvailable=="boolean",themeChanged:M,ready:e=>f(e)&&b(e.sdkVersion)&&e.protocolVersion===v,requestSessionRefresh:e=>f(e)&&(e.reason==="unauthorized"||e.reason==="expiring"),requestAccessRefresh:e=>f(e)&&(e.reason==="forbidden"||e.reason==="manual")};function me(e){if(!f(e)||e.protocol!==A||e.version!==v||typeof e.type!="string"||!Object.hasOwn(F,e.type)||!b(e.instanceId)||e.requestId!==void 0&&!b(e.requestId))return null;let n=e.type;return F[n](e.payload)?w(n,e.instanceId,e.payload,e.requestId):null}var he=new Set(de),$e=new Set(ue);function B(e){return he.has(e.type)}function K(e){if(!f(e)||e.protocol!==A)return{kind:"ignored"};if(e.version!==v)return{kind:"incompatible",version:e.version};let n=me(e);return n?{kind:"message",message:n}:{kind:"ignored"}}function S(e){let n=globalThis.crypto,t=typeof n?.randomUUID=="function"?n.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,12)}`;return`${e}-${t}`}var h=class extends Error{constructor(t,r){super(r);this.code=t;this.name="PluginSdkError"}},x=class extends h{constructor(t,r,s,i){super("http",`${r} ${s} failed with ${t}`);this.status=t;this.method=r;this.url=s;this.body=i;this.name="PluginApiError"}};function ye(e,n,t){let r=n.split(/[/?#]/).includes("..");if(/^[a-z][a-z0-9+.-]*:/i.test(n)||n.startsWith("//")||r)throw new h("invalid_url",`plugin API paths are relative to ${e}; got ${n}`);let s=n.replace(/^\/+/,""),i=e.replace(/\/+$/,""),o=s?`${i}/${s}`:i;if(t){let a=new URLSearchParams;for(let[u,c]of Object.entries(t))c!==void 0&&a.set(u,String(c));let d=a.toString();d&&(o+=(o.includes("?")?"&":"?")+d)}return o}function fe(e){return e==null||typeof e=="string"||typeof ReadableStream>"u"||!(e instanceof ReadableStream)}function Pe(e,n){let t=i=>{try{return new URL(i,"http://plugin.invalid").pathname}catch{return i}},r=t(e).replace(/\/+$/,""),s=t(n);return s.startsWith(`${r}/`)?/^\/accounts\/(\d{12})(?:\/|$)/.exec(s.slice(r.length))?.[1]??null:null}function Y(e){return fe(e.body)&&(e.account===null||e.permitted.some(n=>n.accountNumber===e.account))}function Te(e){return Y(e)&&e.rejectedToken!==e.retryToken}var J=()=>new h("timeout","the request timed out");function be(e,n){if(!n)return e;if(typeof AbortSignal.timeout=="function"&&(!e||typeof AbortSignal.any=="function")){let i=AbortSignal.timeout(n);return e?AbortSignal.any([e,i]):i}let t=new AbortController,r=setTimeout(()=>t.abort(J()),n),s=()=>{clearTimeout(r),t.abort(e?.reason)};return e?.aborted?s():e?.addEventListener("abort",s,{once:!0}),t.signal}function W(e){return typeof e=="object"&&e!==null&&e.name==="TimeoutError"?J():e}async function ve(e){if(e.status===204||e.status===205)return;let n=await e.text();if(n){if(/json/i.test(e.headers.get("content-type")??""))try{return JSON.parse(n)}catch{return n}return n}}function Q(e){let n=(i,o)=>ye(e.apiBaseUrl,i,o),t=async(i,o,a)=>{e.assertActive();let d=()=>({body:a.body,account:Pe(e.apiBaseUrl,i),permitted:e.accounts()}),u=be(a.signal,a.timeoutMs),c=m=>e.fetch(i,{method:o,headers:{...a.headers,Authorization:`Bearer ${m}`},body:a.body??void 0,signal:u});try{let m=await e.tokens.token(),g=await c(m);if(g.status===401&&Y(d())){e.assertActive();let p=await e.tokens.refresh("unauthorized");Te({...d(),rejectedToken:m,retryToken:p.token})&&(g=await c(p.token))}return g.status===403&&e.onForbidden?.(),g}catch(m){throw W(m)}},r=(i,o={})=>t(n(i,o.query),(o.method??"GET").toUpperCase(),o),s=async(i,o,a,d={})=>{let u=n(o,d.query),c={Accept:"application/json",...d.headers},m;a!==void 0&&(c["Content-Type"]="application/json",m=JSON.stringify(a));let g=await t(u,i,{...d,headers:c,body:m}),p;try{p=await ve(g)}catch(y){throw W(y)}if(!g.ok)throw new x(g.status,i,u,p);return p};return{request:r,url:n,get:(i,o)=>s("GET",i,void 0,o),post:(i,o,a)=>s("POST",i,o,a),put:(i,o,a)=>s("PUT",i,o,a),patch:(i,o,a)=>s("PATCH",i,o,a),delete:(i,o)=>s("DELETE",i,void 0,o)}}var X="infra-client-plugin-theme";function Z(e,n){let t=z(e),r=n.getElementById(X);if(r)r.textContent=t;else{let s=n.createElement("style");s.id=X,s.textContent=t,n.head.appendChild(s)}n.documentElement.setAttribute("data-theme",e.appearance)}var xe="0.2.0",Ee=15e3,we=3e4,ke=1e3;function Ce(e){let n=globalThis;if(!e.hostWindow&&n.window&&n.window.parent===n.window)throw new h("not_framed","this page is not inside the portal's plugin frame");let t=e.hostWindow??n.window?.parent,r=e.listenOn??n.window,s=e.hostOrigin??n.window?.location.origin,i=e.fetch??(typeof n.fetch=="function"?(u,c)=>n.fetch(u,c):void 0),o=[!t&&"hostWindow",!r&&"listenOn",!s&&"hostOrigin",!i&&"fetch"].filter(u=>typeof u=="string");if(!t||!r||!s||!i)throw new h("invalid_options",`outside a browser window, pass ${o.join(", ")}`);let a=n.document,d=e.applyTheme===!1?null:e.applyTheme??(a?u=>Z(u,a):null);return{hostWindow:t,hostOrigin:s,listenOn:r,fetch:i,applyTheme:d}}function ee(e,n){return e.source!==n.hostWindow||e.origin!==n.hostOrigin?null:K(e.data)}function ne(e={}){let n;try{n=Ce(e)}catch(s){return Promise.reject(s)}let t=e.instanceId??S("frame"),r=e.handshakeTimeoutMs??V;return new Promise((s,i)=>{let o,a,d=()=>{n.listenOn.removeEventListener("message",m),clearInterval(o),clearTimeout(a)},u=g=>{d(),i(g)},c=()=>{try{n.hostWindow.postMessage(w("ready",t,{sdkVersion:xe,protocolVersion:v}),n.hostOrigin)}catch(g){u(new h("invalid_options",`cannot post to the host: ${g instanceof Error?g.message:String(g)}`))}},m=g=>{let p=ee(g,n);if(!p)return;if(p.kind==="incompatible"){u(new h("incompatible_host",`the host speaks protocol version ${String(p.version)}`));return}if(p.kind!=="message"||p.message.type!=="init"||p.message.instanceId!==t)return;d();let y=new N(n,t,p.message.payload,e);n.applyTheme&&(n.applyTheme(y.theme()),y.on("themeChanged",n.applyTheme)),s(y)};n.listenOn.addEventListener("message",m),o=setInterval(c,ke),r>0&&(a=setTimeout(()=>u(new h("handshake_timeout",`no init from the host within ${r} ms`)),r)),c()})}var N=class{constructor(n,t,r,s){this.env=n;this.instanceId=t;this.user=r.user,this.plugin=r.plugin,this.entry=r.entry,this.current=r.session,this.access={accounts:r.accounts,entryAvailable:!0},this.currentTheme=r.theme,this.refreshTimeoutMs=s.refreshTimeoutMs??Ee,this.refreshLeadMs=s.refreshLeadMs??we,this.session={current:()=>this.current,token:()=>this.tokenForRequest(),refresh:i=>this.refresh(i)},this.api=Q({apiBaseUrl:r.plugin.apiBaseUrl,tokens:this.session,accounts:()=>this.access.accounts,fetch:n.fetch,assertActive:()=>this.assertActive(),onForbidden:s.accessRefreshOnForbidden===!1?void 0:()=>this.requestAccessRefresh("forbidden")}),this.listener=i=>{let o=ee(i,n);o?.kind==="message"&&o.message.instanceId===t&&B(o.message)&&this.handle(o.message)},n.listenOn.addEventListener("message",this.listener)}user;plugin;entry;session;api;current;access;currentTheme;askedFor=null;ended=!1;pending=null;listeners=new Map;refreshTimeoutMs;refreshLeadMs;listener;state(){return this.ended?"ended":"connected"}accounts(){return this.access.accounts}permissionsFor(n){return this.access.accounts.find(t=>t.accountNumber===n)?.permissions??[]}hasPermission(n,t){return this.permissionsFor(t).includes(n)}entryAvailable(){return this.access.entryAvailable}theme(){return this.currentTheme}requestAccessRefresh(n="manual"){this.ended||this.post("requestAccessRefresh",{reason:n})}on(n,t){let r=this.listeners.get(n);return r||this.listeners.set(n,r=new Set),r.add(t),()=>r.delete(t)}dispose(){this.ended||(this.teardown(),this.pending?.reject(new h("disposed","the plugin context was disposed")),this.listeners.clear())}assertActive(){if(this.ended)throw new h("session_ended","the host ended this session")}post(n,t,r){this.env.hostWindow.postMessage(w(n,this.instanceId,t,r),this.env.hostOrigin)}async tokenForRequest(){this.assertActive();let n=this.current.expiresAt-Date.now();return n<=0?await this.refresh("expiring"):n<=this.refreshLeadMs&&this.askedFor!==this.current.expiresAt&&(this.askedFor=this.current.expiresAt,this.refresh("expiring").catch(()=>{})),this.current.token}refresh(n){if(this.ended)return Promise.reject(new h("session_ended","the host ended this session"));if(this.pending)return this.pending.promise;let t,r,s=new Promise((d,u)=>{t=d,r=u}),i,o=()=>{clearTimeout(i),this.pending=null},a={promise:s,resolve:d=>{o(),t(d)},reject:d=>{o(),r(d)}};return this.refreshTimeoutMs>0&&(i=setTimeout(()=>a.reject(new h("refresh_timeout",`no session from the host within ${this.refreshTimeoutMs} ms`)),this.refreshTimeoutMs)),this.pending=a,this.post("requestSessionRefresh",{reason:n},S("req")),s}handle(n){switch(n.type){case"sessionChanged":this.current=n.payload,this.pending?.resolve(n.payload),this.emit("sessionChanged",n.payload);return;case"sessionEnded":this.teardown(),this.pending?.reject(new h("session_ended",`the host ended this session (${n.payload.reason})`)),this.emit("sessionEnded",n.payload),this.listeners.clear();return;case"accessChanged":this.access=n.payload,this.emit("accessChanged",n.payload);return;case"themeChanged":this.currentTheme=n.payload,this.emit("themeChanged",n.payload);return;default:return}}teardown(){this.ended=!0,this.env.listenOn.removeEventListener("message",this.listener)}emit(n,t){let r=this.listeners.get(n);if(r)for(let s of[...r])try{s(t)}catch(i){let o=globalThis.reportError;typeof o=="function"?o(i):setTimeout(()=>{throw i},0)}}};var te=`
6
+ /* The page itself: see-through so the portal's own surface shows behind the frame, in the portal's
7
+ text colour and type. :where() keeps these at zero specificity, so a plugin's own rules win. */
8
+ :where(html, body) { background: transparent; }
9
+ :where(body) { margin: 0; color: var(--surface-foreground); font-family: var(--font-sans, system-ui, sans-serif); font-size: 0.875rem; line-height: 1.5; }
10
+
11
+ .ic-page { display: flex; flex-direction: column; gap: 1rem; color: var(--surface-foreground); font-family: var(--font-sans, system-ui, sans-serif); font-size: 0.875rem; line-height: 1.5; }
12
+ .ic-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
13
+ .ic-title { margin: 0; font-size: 1.75rem; font-weight: 500; letter-spacing: -0.02em; color: var(--surface-foreground); }
14
+ .ic-card-title { margin: 0; font-size: 0.875rem; font-weight: 500; letter-spacing: -0.01em; color: var(--surface-foreground); }
15
+ .ic-subtitle { margin: 0; font-size: 0.9375rem; font-weight: 600; color: var(--surface-foreground); }
16
+ .ic-muted { color: var(--muted-foreground); }
17
+ .ic-caption { font-size: 0.75rem; color: var(--muted-foreground); }
18
+ .ic-mono { font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-variant-numeric: tabular-nums; }
19
+ .ic-actions { display: flex; align-items: center; gap: 0.5rem; }
20
+
21
+ .ic-card { border: 1px solid var(--border); border-radius: var(--radius-lg, 0.75rem); background: var(--card-bg, transparent); padding: 1rem 1.25rem; display: flex; flex-direction: column; gap: 0.75rem; }
22
+ .ic-card-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
23
+
24
+ /* Wide content scrolls in its own box; a plugin frame must never scroll the portal sideways. */
25
+ .ic-table-wrap { overflow-x: auto; }
26
+ .ic-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
27
+ .ic-table th { text-align: left; font-size: 0.6875rem; font-weight: 500; letter-spacing: 0.02em; text-transform: uppercase; color: var(--muted-foreground); padding: 0 0.75rem 0.5rem; white-space: nowrap; }
28
+ .ic-table td { padding: 0.55rem 0.75rem; border-top: 1px solid var(--border); vertical-align: middle; }
29
+ .ic-table tbody tr:hover { background: var(--state-hover, transparent); }
30
+ .ic-table td:last-child, .ic-table th:last-child { text-align: right; }
31
+
32
+ /* Control scale shared with the portal: sm 24px, md 32px, lg 36px. */
33
+ .ic-btn { display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center; gap: 0.5rem; white-space: nowrap; height: 2rem; padding: 0 0.75rem; border: 0; border-radius: var(--radius, 0.5rem); font: inherit; font-size: 0.875rem; cursor: pointer; transition: background-color 120ms var(--ease-standard, ease); }
34
+ .ic-btn:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
35
+ .ic-btn:disabled { opacity: 0.5; cursor: not-allowed; }
36
+ .ic-btn-sm { height: 1.5rem; padding: 0 0.625rem; font-size: 0.75rem; font-weight: 500; }
37
+ .ic-btn-lg { height: 2.25rem; padding: 0 1rem; }
38
+ .ic-btn-primary { background: var(--brand-primary); color: var(--brand-foreground); }
39
+ .ic-btn-primary:hover:not(:disabled) { background: var(--brand-primary-hover, var(--brand-primary)); }
40
+ /* Alpha fills, like the portal's: they tune independently of the accent scale and survive rebranding. */
41
+ .ic-btn-secondary { background: color-mix(in srgb, var(--brand-primary) 12%, transparent); color: var(--brand-muted-foreground, var(--brand-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand-primary) 25%, transparent); }
42
+ .ic-btn-secondary:hover:not(:disabled) { background: color-mix(in srgb, var(--brand-primary) 18%, transparent); }
43
+ .ic-btn-outline { background: transparent; color: var(--surface-foreground); font-weight: 500; box-shadow: inset 0 0 0 1px var(--border-strong); }
44
+ .ic-btn-outline:hover:not(:disabled) { background: var(--muted, var(--state-hover)); }
45
+ .ic-btn-ghost { background: transparent; color: var(--muted-foreground); }
46
+ .ic-btn-ghost:hover:not(:disabled) { background: var(--muted, var(--state-hover)); }
47
+ .ic-btn-danger { background: transparent; color: var(--danger); font-weight: 500; box-shadow: inset 0 0 0 1px var(--border-strong); }
48
+ .ic-btn-danger:hover:not(:disabled) { background: var(--danger-muted, transparent); }
49
+
50
+ .ic-field { display: flex; flex-direction: column; gap: 0.25rem; }
51
+ .ic-label { font-size: 0.75rem; font-weight: 500; color: var(--muted-foreground); }
52
+ .ic-input, .ic-select { height: 2.25rem; padding: 0 0.5rem; font: inherit; font-size: 0.875rem; color: var(--surface-foreground); background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius, 0.5rem); }
53
+ .ic-input:focus-visible, .ic-select:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 1px; }
54
+
55
+ .ic-badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 0.05rem 0.45rem; font-size: 0.6875rem; white-space: nowrap; background: var(--card-bg); color: var(--muted-foreground); }
56
+ .ic-badge-brand { background: color-mix(in srgb, var(--brand-primary) 14%, transparent); color: var(--brand-muted-foreground, var(--brand-primary)); }
57
+ .ic-badge-green { background: var(--success-muted, transparent); color: var(--success); }
58
+ .ic-badge-amber { background: var(--warning-muted, transparent); color: var(--warning); }
59
+ .ic-badge-red { background: var(--danger-muted, transparent); color: var(--danger); }
60
+
61
+ /* Every plugin needs these three, and a plugin that improvises them is where the look breaks down. */
62
+ .ic-empty, .ic-error { border: 1px solid var(--border); border-radius: var(--radius-lg, 0.75rem); background: var(--card-bg, transparent); padding: 1.5rem; display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; }
63
+ .ic-empty p, .ic-error p { margin: 0; }
64
+ .ic-error { border-left: 3px solid var(--danger); }
65
+ .ic-error .ic-error-title { color: var(--danger); font-weight: 500; }
66
+
67
+ .ic-skeleton { height: 1rem; border-radius: var(--radius, 0.5rem); background: var(--muted, var(--card-bg)); animation: ic-pulse 1.4s ease-in-out infinite; }
68
+ .ic-skeleton + .ic-skeleton { margin-top: 0.5rem; }
69
+ @keyframes ic-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
70
+ @media (prefers-reduced-motion: reduce) { .ic-skeleton { animation: none; } }
71
+ `,I="infra-client-plugin-ui";function _(e){let n=e.getElementById(I);if(n){n.textContent=te;return}let t=e.createElement("style");t.id=I,t.textContent=te,e.head.appendChild(t)}var Me=()=>document.getElementById("app");function l(e,n,t){let r=document.createElement(e);return n&&(r.className=n),t!==void 0&&(r.textContent=t),r}var E=(e,n="outline")=>l("button",`ic-btn ic-btn-sm ic-btn-${n}`,e);function U(...e){Me().replaceChildren(...e.filter(n=>n!==null))}function Ae(){let e=l("div","ic-card");for(let n=0;n<3;n+=1)e.appendChild(l("div","ic-skeleton"));return e}function k(e,n){let t=l("div","ic-empty");return t.appendChild(l("p",void 0,e)),n&&t.appendChild(l("p","ic-caption",n)),t}function re(e,n){let t=l("div","ic-error");if(t.appendChild(l("p","ic-error-title","That did not work")),t.appendChild(l("p","ic-caption",e)),n){let r=E("Try again","outline");r.addEventListener("click",n),t.appendChild(r)}return t}async function Se(){_(document);let e;try{e=await ne()}catch(n){U(re(n.message));return}e.on("accessChanged",()=>void C(e)),e.on("sessionEnded",()=>U(k("Your session ended.","Sign in again in the portal."))),await C(e)}var P=null,T=null;function ie(e){let n=e.accounts();return P&&!n.some(t=>t.accountNumber===P)&&(T=P,P=null),T&&n.some(t=>t.accountNumber===T)&&(P=T,T=null),!P&&!T&&(P=n[0]?.accountNumber??null),n.find(t=>t.accountNumber===P)??null}function Ne(e){let n=l("div","ic-header"),t=l("div");t.appendChild(l("h1","ic-title","Parameters")),t.appendChild(l("p","ic-caption",`${e.plugin.displayName} \xB7 ${e.plugin.version}`)),n.appendChild(t);let r=e.accounts(),s=ie(e);if(r.length>1||r.length===1&&!s){let i=l("select","ic-select");if(i.setAttribute("aria-label","Account"),!s){let o=l("option",void 0,"Choose an account");o.value="",o.selected=!0,o.disabled=!0,i.appendChild(o)}for(let o of r){let a=l("option",void 0,`${o.accountNumber} \xB7 ${o.displayName}`);a.value=o.accountNumber,a.selected=o.accountNumber===s?.accountNumber,i.appendChild(a)}i.addEventListener("change",()=>{P=i.value,T=null,C(e)}),n.appendChild(i)}else s&&n.appendChild(l("span","ic-caption ic-mono",`${s.accountNumber} \xB7 ${s.displayName}`));return n}var L=0;async function C(e){let n=++L,t=(...y)=>U(Ne(e),...y),r=ie(e);if(!r){t(T?k(`Your access to account ${T} ended.`,e.accounts().length?"Choose another account to continue.":void 0):k("No account available.","You have no account with access to this plugin."));return}let s=e.entry==="admin"&&e.hasPermission("admin",r.accountNumber);t(Ae());let i,o;try{let y=await e.api.get(`accounts/${r.accountNumber}/parameters`);i=y.parameters,o=y.prefixes}catch(y){n===L&&t(re(O(y),()=>void C(e)));return}if(n!==L)return;if(!i.length){t(k("No parameters yet.",`Nothing exists under ${o.join(", ")}.`));return}let a=l("div","ic-card"),d=l("div","ic-card-head");d.appendChild(l("h2","ic-card-title",`${i.length} parameter${i.length===1?"":"s"}`)),d.appendChild(l("span","ic-caption",o.join(", "))),a.appendChild(d);let u=l("div","ic-table-wrap"),c=l("table","ic-table"),m=l("thead"),g=l("tr");for(let[y,R]of[["Name",""],["Value",""],["Type",""],["",""]]){let H=l("th",void 0,y);R&&(H.style.width=R),g.appendChild(H)}m.appendChild(g),c.appendChild(m);let p=l("tbody");for(let y of i)p.appendChild(Ie(e,r.accountNumber,y,s));c.appendChild(p),u.appendChild(c),a.appendChild(u),t(a)}function Ie(e,n,t,r){let s=t.type==="SecureString",i=l("tr");i.appendChild(l("td","ic-mono",t.name));let o=l("td","value ic-mono ic-muted","\u2014");i.appendChild(o);let a=l("td");a.appendChild(l("span",`ic-badge${s?" ic-badge-amber":""}`,s?"secret":t.type)),i.appendChild(a);let d=l("td"),u=E(s?"Details":"Show");return u.addEventListener("click",async()=>{u.disabled=!0;try{let c=await e.api.get(`accounts/${n}/parameters/${encodeURIComponent(t.name)}`);if(c.parameter.secret){o.textContent="hidden \u2014 encrypted",u.remove();return}o.textContent=c.parameter.value??"",o.className="value ic-mono",u.remove(),r&&d.appendChild(oe(e,n,t.name,o,d))}catch(c){o.replaceChildren(l("span","ic-caption",O(c))),u.disabled=!1}}),d.appendChild(u),i.appendChild(d),i}function oe(e,n,t,r,s){let i=E("Edit");return i.addEventListener("click",()=>{let o=r.textContent??"",a=l("input","ic-input");a.value=o,a.setAttribute("aria-label",`New value for ${t}`);let d=E("Save","primary"),u=E("Cancel","ghost"),c=l("div","editing");c.append(a,d,u),r.replaceChildren(c),i.remove(),a.focus();let m=g=>{r.replaceChildren(),r.textContent=g,s.appendChild(oe(e,n,t,r,s))};u.addEventListener("click",()=>m(o)),d.addEventListener("click",async()=>{if(window.confirm(`Set ${t} to "${a.value}"?`)){d.disabled=!0;try{await e.api.put(`accounts/${n}/parameters/${encodeURIComponent(t)}`,{value:a.value}),m(a.value)}catch(g){d.disabled=!1;let p=l("span","ic-caption",O(g));c.appendChild(p)}}})}),i}function O(e){if(e instanceof x){let n=e.body;return e.status===403?"You no longer have access to this plugin in this account.":n?.message??`The request failed (${e.status}).`}return e.message||"Something went wrong."}Se();})();
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Application parameters</title>
7
+ <link rel="stylesheet" href="app.css" />
8
+ </head>
9
+ <body>
10
+ <!-- The portal paints this document's theme tokens; nothing here hardcodes a colour or a product name. -->
11
+ <main id="app" class="ic-page" aria-live="polite">
12
+ <p class="muted">Connecting to the portal…</p>
13
+ </main>
14
+ <script src="app.js"></script>
15
+ </body>
16
+ </html>