@hazem3500/palette 0.9.110 → 0.9.112
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/NODE.md +90 -0
- package/README.md +25 -0
- package/dist/{account-tokens-LBRVZAEJ.js → account-tokens-YO6MIZFF.js} +1 -1
- package/dist/chunk-CF4GJN3B.js +1 -0
- package/dist/index.js +1 -1
- package/dist/node.mjs +382 -298
- package/dist/postgres/008_webhook_outbox.sql +14 -0
- package/dist/public-env.d.ts +4 -0
- package/dist/react.js +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +366 -289
- package/dist/setup.js +1 -1
- package/dist/{snapshot-3PSQKXUV.js → snapshot-YYM5GW6I.js} +1 -1
- package/dist/snapshot.d.ts +3 -2
- package/package.json +1 -1
- package/server/index.ts +75 -79
- package/server/migrations/0022_webhook_outbox.sql +14 -0
- package/server/node-webhooks.ts +5 -0
- package/server/proxy-auth.ts +2 -0
- package/server/public-env.ts +4 -0
- package/server/setup-client.txt +55 -55
- package/server/tokens.ts +14 -0
- package/server/webhooks.ts +98 -13
- package/dist/chunk-U55ASP2Y.js +0 -1
package/dist/setup.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as n,d as o}from"./chunk-
|
|
1
|
+
import{a as n,d as o}from"./chunk-CF4GJN3B.js";import"./chunk-KLPQTFVH.js";import"./chunk-RBHY7UDS.js";import"./chunk-LMXFSXO6.js";import"./chunk-DU7H4TEX.js";import"./chunk-5HZI7HRR.js";import"./chunk-2BS2OWWH.js";import"./chunk-J47R52WX.js";import"./chunk-VX6XCU4E.js";import"./chunk-4HBBEVFK.js";function r(r){return(t={})=>o(n({...r,...t}))}var t=r;export{t as defineKomo,r as definePalette};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var e="data-palette-no-snapshot",t=`[${e}],[data-komo-no-snapshot]`,a=e=>e.matches(t),n="[data-branch-comments],[data-palette-move-clone],[data-palette-viewport],[data-palette-snapshot-sandbox]",r="[redacted]",o=new Set(["script","noscript","template","style","link","meta","base","title"]),i=/^(?:href|src|action|formaction|xlink:href|poster|data|background)$/i,l=e=>"textarea"===e.localName||"input"===e.localName&&!/^(?:button|submit|reset|image|checkbox|radio|range|color)$/i.test(e.getAttribute("type")??""),s=2e4,c=200,h=500,d=(e,t)=>e.length>t?`${e.slice(0,t-1)}…`:e,u=e=>(new TextEncoder).encode(e).length,m=e=>!!e.closest(`${t},${n}`);function f(e,t=1/0){for(let{name:a,value:n}of[...e.attributes])/^on/i.test(a)||"srcdoc"===a||/^data-(palette|komo)-/.test(a)&&!/-no-snapshot$/.test(a)||i.test(a)&&/^\s*(?:javascript|vbscript):/i.test(n)?e.removeAttribute(a):n.length>t&&e.setAttribute(a,d(n,t))}function p(e,t,i=s){let c=e.ownerDocument,h=i,m=e=>{if(3===e.nodeType){let t=d(e.nodeValue??"",500);return h-=t.length,c.createTextNode(t)}if(1!==e.nodeType)return null;let i=e;if(o.has(i.localName)||i.matches(n))return null;let s=i.cloneNode(!1);if(f(s,200),l(i)&&function(e,t){let a=e.value??e.getAttribute("value")??"";"textarea"===e.localName?t.textContent=a?r:"":(a||t.hasAttribute("value"))&&t.setAttribute("value",r)}(i,s),h-=s.outerHTML.length,a(i))return s;if(t?.(i))return l(i)||(s.textContent=r),s;if("textarea"===i.localName)return s;for(let e of i.childNodes){if(h<=0){s.append(c.createComment(" trimmed "));break}let t=m(e);t&&s.append(t)}return s},p=m(e);if(!p)return"";let g=p.outerHTML;if(u(g)<=i)return g;let w=c.createComment(" trimmed ");p.append(w);for(let e=u(p.outerHTML)-i;e>0&&p.firstChild!==w;e=u(p.outerHTML)-i)for(;e>0&&p.firstChild!==w;){let t=w.previousSibling;for(;t.lastChild;)t=t.lastChild;e-=1===t.nodeType?t.outerHTML.length:(t.nodeValue?.length??0)+7,t.parentNode.removeChild(t)}return g=p.outerHTML,u(g)>i?"":g}var g=class extends Error{constructor(e){super(e),this.reason=e}reason},w=25e4,y=e=>new Promise((t,a)=>{let n=new FileReader;n.onload=()=>t(n.result),n.onerror=()=>a(n.error),n.readAsDataURL(e)});async function b(e,t){if(e.startsWith("data:"))return e;let a=new URL(e,t.document.baseURI);try{let e=await fetch(a,{mode:"cors",signal:AbortSignal.timeout(5e3)});return e.ok?await y(await e.blob()):""}catch{if(a.origin!==t.location.origin)throw new g("cross-origin");return""}}function x(e,t,a){let n="";for(let r=0;r<e.length;r++){let o=e[r];if(o.startsWith("--"))continue;let i=e.getPropertyValue(o);(i!==t.get(o)||a&&i!==a.getPropertyValue(o))&&(n+=`${o}:${i};`)}return n}var v=/url\((['"]?)(.*?)\1\)/g;async function $(e,t,i){let s=e.ownerDocument,c=s.defaultView,h=e.getBoundingClientRect(),d=e.offsetWidth??h.width,u=Math.min(e.offsetHeight??h.height,2e3);if(!d||!u)throw new g("failed");let m=function(){let e=document.createElement("iframe");e.setAttribute("data-palette-snapshot-sandbox",""),e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.style.cssText="position:fixed;left:-10px;top:0;width:1px;height:1px;border:0;opacity:0;pointer-events:none",document.documentElement.append(e);let t=e.contentDocument;try{t.open(),t.write("<!doctype html><html><head></head><body></body></html>"),t.close()}catch{}let a=new Map;return{of(e){let n=`${e.namespaceURI} ${e.localName}`,r=a.get(n);if(!r){let o=t.createElementNS(e.namespaceURI,e.localName);(t.body??t.documentElement).append(o);let i=t.defaultView.getComputedStyle(o);r=new Map;for(let e=0;e<i.length;e++)r.set(i[e],i.getPropertyValue(i[e]));o.remove(),a.set(n,r)}return r},dispose:()=>e.remove()}}(),p=[],w=new Set,y=new Map,$=[],M="",C=0,T=e=>(e.catch(()=>{}),e),A=e=>{for(let[,,t]of e.matchAll(v))t&&!t.startsWith("data:")&&!y.has(t)&&y.set(t,T(b(t,c)));return e},E=(e,h)=>{if(3===e.nodeType)return s.createTextNode(e.nodeValue??"");if(1!==e.nodeType)return null;let y=e,v=y.localName;if(o.has(v)||y.matches(n))return null;if(++C>3e3)throw new g("too-large");let I,L=c.getComputedStyle(y);if("none"===L.display)return null;if("canvas"===v||"video"===v){I=s.createElement("img");let e=document.createElement("canvas");e.width=y.width||y.videoWidth||1,e.height=y.height||y.videoHeight||1;try{e.getContext("2d").drawImage(y,0,0,e.width,e.height),I.setAttribute("src",e.toDataURL("image/png"))}catch{throw new g("cross-origin")}}else if("iframe"===v||"embed"===v||"object"===v)I=s.createElement("div");else{I=y.cloneNode(!1),f(I);for(let e of["id","class","srcset","sizes","loading","style"])I.removeAttribute(e)}l(y)&&(I.removeAttribute("value"),I.hasAttribute("placeholder")&&t?.(y)&&I.setAttribute("placeholder",r)),"img"===v&&p.push(T(async function(e,t,a){let n=e.currentSrc||e.src;if(!n||n.startsWith("data:"))return n;await e.decode().catch(()=>{});let r=Math.min(e.naturalWidth,Math.ceil(e.offsetWidth*t)||e.naturalWidth),o=Math.round(r*e.naturalHeight/(e.naturalWidth||1));if(r&&o){let t=document.createElement("canvas");t.width=r,t.height=o;try{return t.getContext("2d").drawImage(e,0,0,r,o),t.toDataURL("image/png")}catch{}}return b(n,a)}(y,i,c).then(e=>I.setAttribute("src",e)))),w.add(L.fontFamily.split(",")[0].replace(/["']/g,"").trim().toLowerCase());let R=x(L,m.of(y),h);h||(R+=`${"inline"===L.display?"display:inline-block;":""}margin:0;position:relative;inset:auto;float:none;transform:none;translate:none;rotate:none;scale:none;flex:none;box-sizing:border-box;width:${d}px;height:${u}px;max-width:none;max-height:none;min-width:0;min-height:0;`);let P=a(y)&&!!h;P&&(R+="color:transparent;background:#e5e5e5;background-image:none;"),$.push([I,A(R)]);for(let e of["::before","::after"]){let t=c.getComputedStyle(y,e);P||/^(?:none|normal)$/.test(t.content)||(I.setAttribute("data-snapshot",String(C)),M+=`[data-snapshot="${C}"]${e}{${A(x(t,m.of(y),L))}content:${t.content};}`)}if(P)return I;if(t?.(y))return l(y)||(I.textContent=r),I;if("textarea"===v)return I;for(let e of y.childNodes){if("picture"===v&&"img"!==e.localName)continue;let t=E(e,L);t&&I.append(t)}return I};try{await(s.fonts?.ready);let t=E(e),a=new Map(await Promise.all([...y].map(async([e,t])=>[e,await t]))),n=e=>e.replace(v,(e,t,n)=>a.has(n)?`url("${a.get(n)}")`:e);for(let[e,t]of $)e.setAttribute("style",n(t));M=n(M),await Promise.all(p);let r=s.createElement("div");r.setAttribute("style",`width:${d+32}px;height:${u+32}px;padding:16px;box-sizing:border-box;overflow:hidden;background:${function(e,t){for(let a=e.parentElement;a;a=a.parentElement){let e=t.getComputedStyle(a).backgroundColor;if(e&&!/^(?:transparent|rgba\(.*,\s*0\))$/.test(e))return e}return"#fff"}(e,c)}`);let o=s.createElement("style");o.textContent=await async function(e,t,a){let n=[],r=2e6,o=async(e,i)=>{for(let l of e){if(l.cssRules&&await o(l.cssRules,i),!l.cssText.startsWith("@font-face"))continue;let e=l.style,s=e.getPropertyValue("font-family").replace(/["']/g,"").trim().toLowerCase(),c=e.getPropertyValue("src").match(/url\((['"]?)(.*?)\1\)/)?.[2];if(!t.has(s)||!c||r<=0)continue;let h=await b(new URL(c,i).href,a).catch(()=>"");h&&(r-=h.length,n.push(`@font-face{font-family:"${s}";src:url("${h}");${["font-weight","font-style","font-stretch","unicode-range"].map(t=>e.getPropertyValue(t)?`${t}:${e.getPropertyValue(t)};`:"").join("")}}`))}};for(let t of e.styleSheets){let a;try{a=t.cssRules}catch{continue}await o(a,t.href??e.baseURI)}return n.join("")}(s,w,c)+M,r.append(o,t);let i={width:d+32,height:u+32},l=`<svg xmlns="http://www.w3.org/2000/svg" width="${i.width}" height="${i.height}"><foreignObject x="0" y="0" width="100%" height="100%">${(new XMLSerializer).serializeToString(r)}</foreignObject></svg>`,h=new Image;return h.src=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(l)}`,await h.decode(),{image:h,...i}}finally{m.dispose()}}var M=(e,t)=>new Promise((a,n)=>{try{e.toBlob(a,"image/webp",t)}catch(e){n(e)}});async function C(e,t={}){let a=t.maxBytes??w,n=e.ownerDocument.defaultView??window,r=e.offsetWidth||e.getBoundingClientRect().width,o=Math.min(n.devicePixelRatio||1,2,1200/(r+32)),i=await $(e,t.redact,o),l=document.createElement("canvas"),s=.8;for(let e=0;e<12&&i.width*o>=48;e++){l.width=Math.max(1,Math.round(i.width*o)),l.height=Math.max(1,Math.round(i.height*o));let e,t=l.getContext("2d");if(!t)throw new g("unsupported");t.drawImage(i.image,0,0,l.width,l.height);try{e=await M(l,s)}catch{throw new g("cross-origin")}if(!e||"image/webp"!==e.type)throw new g("unsupported");if(e.size<=a)return y(e);s>.5?s=Math.round(100*(s-.15))/100:o*=Math.min(.85,Math.max(.3,.95*Math.sqrt(a/e.size)))}throw new g("too-large")}var T={"cross-origin":"the element shows images from another origin","too-large":"the element is too large to draw",unsupported:"this browser can't encode WebP",failed:"it couldn't be drawn"},A=e=>`\x3c!-- palette: no image snapshot, ${T[e]}. --\x3e`;async function E(e,t,a=s){if(!e?.isConnected||m(e))return null;let n={},r=()=>p(e,t.redact,a-120);if(t.html&&(n.html=r()),t.image){await new Promise(e=>"function"==typeof requestIdleCallback?requestIdleCallback(e,{timeout:500}):setTimeout(e));try{n.image=await C(e,t)}catch(e){n.note=e instanceof g?e.reason:"failed","cross-origin"===n.note&&(n.html??=r()),n.html&&(n.html=A(n.note)+n.html)}}return n.image||n.html?n:null}var I=e=>{let[t,a]=e.split(","),n=atob(a),r=new Uint8Array(n.length);for(let e=0;e<n.length;e++)r[e]=n.charCodeAt(e);return new Blob([r],{type:t.slice(5,t.indexOf(";"))})},L=new WeakMap;function R(e,t,a,n,r){let o=L.get(e);if(!o){let r={...a,maxBytes:Math.min(a.maxBytes??w,n.maxImageBytes||1/0)};o=a.image||a.html?E(t(e),r,Math.min(s,n.maxHtmlBytes||1/0)).catch(()=>null):Promise.resolve(null),L.set(e,o)}return r&&o.then(async e=>{for(let t of["image","html"])e?.[t]&&await r(t,"image"===t?I(e.image):new Blob([e.html],{type:"text/html;charset=utf-8"})).catch(e=>console.info(`Palette: the comment posted without its ${t} snapshot.`,e instanceof Error?e.message:e))}),o}export{c as ATTRIBUTE_LIMIT,s as HTML_LIMIT,w as IMAGE_BYTES,e as OPT_OUT,r as PLACEHOLDER,h as TEXT_LIMIT,E as captureSnapshot,A as noteComment,m as skipped,R as snapshotFor,p as snapshotHtml,C as snapshotImage};
|
|
1
|
+
var e="data-palette-no-snapshot",t=`[${e}],[data-komo-no-snapshot]`,a=e=>e.matches(t),n="[data-branch-comments],[data-palette-move-clone],[data-palette-viewport],[data-palette-snapshot-sandbox]",r="[redacted]",o=new Set(["script","noscript","template","style","link","meta","base","title"]),i=/^(?:href|src|action|formaction|xlink:href|poster|data|background)$/i,l=e=>"textarea"===e.localName||"input"===e.localName&&!/^(?:button|submit|reset|image|checkbox|radio|range|color)$/i.test(e.getAttribute("type")??""),s=2e4,c=200,h=500,d=(e,t)=>e.length>t?`${e.slice(0,t-1)}…`:e,u=e=>(new TextEncoder).encode(e).length,m=e=>!!e.closest(`${t},${n}`);function f(e,t=1/0){for(let{name:a,value:n}of[...e.attributes])/^on/i.test(a)||"srcdoc"===a||/^data-(palette|komo)-/.test(a)&&!/-no-snapshot$/.test(a)||i.test(a)&&/^\s*(?:javascript|vbscript):/i.test(n)?e.removeAttribute(a):n.length>t&&e.setAttribute(a,d(n,t))}function p(e,t,i=s){let c=e.ownerDocument,h=i,m=e=>{if(3===e.nodeType){let t=d(e.nodeValue??"",500);return h-=t.length,c.createTextNode(t)}if(1!==e.nodeType)return null;let i=e;if(o.has(i.localName)||i.matches(n))return null;let s=i.cloneNode(!1);if(f(s,200),l(i)&&function(e,t){let a=e.value??e.getAttribute("value")??"";"textarea"===e.localName?t.textContent=a?r:"":(a||t.hasAttribute("value"))&&t.setAttribute("value",r)}(i,s),h-=s.outerHTML.length,a(i))return s;if(t?.(i))return l(i)||(s.textContent=r),s;if("textarea"===i.localName)return s;for(let e of i.childNodes){if(h<=0){s.append(c.createComment(" trimmed "));break}let t=m(e);t&&s.append(t)}return s},p=m(e);if(!p)return"";let g=p.outerHTML;if(u(g)<=i)return g;let w=c.createComment(" trimmed ");p.append(w);for(let e=u(p.outerHTML)-i;e>0&&p.firstChild!==w;e=u(p.outerHTML)-i)for(;e>0&&p.firstChild!==w;){let t=w.previousSibling;for(;t.lastChild;)t=t.lastChild;e-=1===t.nodeType?t.outerHTML.length:(t.nodeValue?.length??0)+7,t.parentNode.removeChild(t)}return g=p.outerHTML,u(g)>i?"":g}var g=class extends Error{constructor(e){super(e),this.reason=e}reason},w=25e4,y=e=>new Promise((t,a)=>{let n=new FileReader;n.onload=()=>t(n.result),n.onerror=()=>a(n.error),n.readAsDataURL(e)});async function b(e,t){if(e.startsWith("data:"))return e;let a=new URL(e,t.document.baseURI);try{let e=await fetch(a,{mode:"cors",signal:AbortSignal.timeout(5e3)});return e.ok?await y(await e.blob()):""}catch{if(a.origin!==t.location.origin)throw new g("cross-origin");return""}}function x(e,t,a){let n="";for(let r=0;r<e.length;r++){let o=e[r];if(o.startsWith("--"))continue;let i=e.getPropertyValue(o);(i!==t.get(o)||a&&i!==a.getPropertyValue(o))&&(n+=`${o}:${i};`)}return n}var v=/url\((['"]?)(.*?)\1\)/g;async function $(e,t,i){let s=e.ownerDocument,c=s.defaultView,h=e.getBoundingClientRect(),d=e.offsetWidth??h.width,u=Math.min(e.offsetHeight??h.height,2e3);if(!d||!u)throw new g("failed");let m=function(){let e=document.createElement("iframe");e.setAttribute("data-palette-snapshot-sandbox",""),e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.style.cssText="position:fixed;left:-10px;top:0;width:1px;height:1px;border:0;opacity:0;pointer-events:none",document.documentElement.append(e);let t=e.contentDocument;try{t.open(),t.write("<!doctype html><html><head></head><body></body></html>"),t.close()}catch{}let a=new Map;return{of(e){let n=`${e.namespaceURI} ${e.localName}`,r=a.get(n);if(!r){let o=t.createElementNS(e.namespaceURI,e.localName);(t.body??t.documentElement).append(o);let i=t.defaultView.getComputedStyle(o);r=new Map;for(let e=0;e<i.length;e++)r.set(i[e],i.getPropertyValue(i[e]));o.remove(),a.set(n,r)}return r},dispose:()=>e.remove()}}(),p=[],w=new Set,y=new Map,$=[],M="",C=0,T=e=>(e.catch(()=>{}),e),A=e=>{for(let[,,t]of e.matchAll(v))t&&!t.startsWith("data:")&&!y.has(t)&&y.set(t,T(b(t,c)));return e},I=(e,h)=>{if(3===e.nodeType)return s.createTextNode(e.nodeValue??"");if(1!==e.nodeType)return null;let y=e,v=y.localName;if(o.has(v)||y.matches(n))return null;if(++C>3e3)throw new g("too-large");let E,L=c.getComputedStyle(y);if("none"===L.display)return null;if("canvas"===v||"video"===v){E=s.createElement("img");let e=document.createElement("canvas");e.width=y.width||y.videoWidth||1,e.height=y.height||y.videoHeight||1;try{e.getContext("2d").drawImage(y,0,0,e.width,e.height),E.setAttribute("src",e.toDataURL("image/png"))}catch{throw new g("cross-origin")}}else if("iframe"===v||"embed"===v||"object"===v)E=s.createElement("div");else{E=y.cloneNode(!1),f(E);for(let e of["id","class","srcset","sizes","loading","style"])E.removeAttribute(e)}l(y)&&(E.removeAttribute("value"),E.hasAttribute("placeholder")&&t?.(y)&&E.setAttribute("placeholder",r)),"img"===v&&p.push(T(async function(e,t,a){let n=e.currentSrc||e.src;if(!n||n.startsWith("data:"))return n;await e.decode().catch(()=>{});let r=Math.min(e.naturalWidth,Math.ceil(e.offsetWidth*t)||e.naturalWidth),o=Math.round(r*e.naturalHeight/(e.naturalWidth||1));if(r&&o){let t=document.createElement("canvas");t.width=r,t.height=o;try{return t.getContext("2d").drawImage(e,0,0,r,o),t.toDataURL("image/png")}catch{}}return b(n,a)}(y,i,c).then(e=>E.setAttribute("src",e)))),w.add(L.fontFamily.split(",")[0].replace(/["']/g,"").trim().toLowerCase());let R=x(L,m.of(y),h);h||(R+=`${"inline"===L.display?"display:inline-block;":""}margin:0;position:relative;inset:auto;float:none;transform:none;translate:none;rotate:none;scale:none;flex:none;box-sizing:border-box;width:${d}px;height:${u}px;max-width:none;max-height:none;min-width:0;min-height:0;`);let k=a(y)&&!!h;k&&(R+="color:transparent;background:#e5e5e5;background-image:none;"),$.push([E,A(R)]);for(let e of["::before","::after"]){let t=c.getComputedStyle(y,e);k||/^(?:none|normal)$/.test(t.content)||(E.setAttribute("data-snapshot",String(C)),M+=`[data-snapshot="${C}"]${e}{${A(x(t,m.of(y),L))}content:${t.content};}`)}if(k)return E;if(t?.(y))return l(y)||(E.textContent=r),E;if("textarea"===v)return E;for(let e of y.childNodes){if("picture"===v&&"img"!==e.localName)continue;let t=I(e,L);t&&E.append(t)}return E};try{await(s.fonts?.ready);let t=I(e),a=new Map(await Promise.all([...y].map(async([e,t])=>[e,await t]))),n=e=>e.replace(v,(e,t,n)=>a.has(n)?`url("${a.get(n)}")`:e);for(let[e,t]of $)e.setAttribute("style",n(t));M=n(M),await Promise.all(p);let r=s.createElement("div");r.setAttribute("style",`width:${d+32}px;height:${u+32}px;padding:16px;box-sizing:border-box;overflow:hidden;background:${function(e,t){for(let a=e.parentElement;a;a=a.parentElement){let e=t.getComputedStyle(a).backgroundColor;if(e&&!/^(?:transparent|rgba\(.*,\s*0\))$/.test(e))return e}return"#fff"}(e,c)}`);let o=s.createElement("style");o.textContent=await async function(e,t,a){let n=[],r=2e6,o=async(e,i)=>{for(let l of e){if(l.cssRules&&await o(l.cssRules,i),!l.cssText.startsWith("@font-face"))continue;let e=l.style,s=e.getPropertyValue("font-family").replace(/["']/g,"").trim().toLowerCase(),c=e.getPropertyValue("src").match(/url\((['"]?)(.*?)\1\)/)?.[2];if(!t.has(s)||!c||r<=0)continue;let h=await b(new URL(c,i).href,a).catch(()=>"");h&&(r-=h.length,n.push(`@font-face{font-family:"${s}";src:url("${h}");${["font-weight","font-style","font-stretch","unicode-range"].map(t=>e.getPropertyValue(t)?`${t}:${e.getPropertyValue(t)};`:"").join("")}}`))}};for(let t of e.styleSheets){let a;try{a=t.cssRules}catch{continue}await o(a,t.href??e.baseURI)}return n.join("")}(s,w,c)+M,r.append(o,t);let i={width:d+32,height:u+32},l=`<svg xmlns="http://www.w3.org/2000/svg" width="${i.width}" height="${i.height}"><foreignObject x="0" y="0" width="100%" height="100%">${(new XMLSerializer).serializeToString(r)}</foreignObject></svg>`,h=new Image;return h.src=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(l)}`,await h.decode(),{image:h,...i}}finally{m.dispose()}}var M=(e,t)=>new Promise((a,n)=>{try{e.toBlob(a,"image/webp",t)}catch(e){n(e)}});async function C(e,t={}){let a=t.maxBytes??w,n=e.ownerDocument.defaultView??window,r=e.offsetWidth||e.getBoundingClientRect().width,o=Math.min(n.devicePixelRatio||1,2,1200/(r+32)),i=await $(e,t.redact,o),l=document.createElement("canvas"),s=.8;for(let e=0;e<12&&i.width*o>=48;e++){l.width=Math.max(1,Math.round(i.width*o)),l.height=Math.max(1,Math.round(i.height*o));let e,t=l.getContext("2d");if(!t)throw new g("unsupported");t.drawImage(i.image,0,0,l.width,l.height);try{e=await M(l,s)}catch{throw new g("cross-origin")}if(!e||"image/webp"!==e.type)throw new g("unsupported");if(e.size<=a)return y(e);s>.5?s=Math.round(100*(s-.15))/100:o*=Math.min(.85,Math.max(.3,.95*Math.sqrt(a/e.size)))}throw new g("too-large")}var T={"cross-origin":"cross-origin images","too-large":"the element is too large to draw",unsupported:"this browser can't encode WebP",failed:"it couldn't be drawn"},A=e=>`\x3c!-- palette: no image snapshot, ${T[e]}. --\x3e`;async function I(e,t,a=s){if(!e?.isConnected||m(e))return null;let n={},r=()=>p(e,t.redact,a-120);if(t.html&&(n.html=r()),t.image){await new Promise(e=>"function"==typeof requestIdleCallback?requestIdleCallback(e,{timeout:500}):setTimeout(e));try{n.image=await C(e,t)}catch(e){n.note=e instanceof g?e.reason:"failed","cross-origin"===n.note&&(n.html??=r()),n.html&&(n.html=A(n.note)+n.html)}}return n}var E=e=>{let[t,a]=e.split(","),n=Uint8Array.from(atob(a),e=>e.charCodeAt(0));return new Blob([n],{type:t.slice(5,-7)})},L=new WeakMap;function R(e,t,a,n,r){let o=L.get(e);if(!o){let r={...a,maxBytes:Math.min(a.maxBytes??w,n.maxImageBytes||1/0)};o=a.image||a.html?I(t(e),r,Math.min(s,n.maxHtmlBytes||1/0)).catch(()=>({note:"failed"})):Promise.resolve(null),L.set(e,o)}return r&&o.then(async e=>{let t="failed"===e?.note;for(let a of["image","html"]){let n=e?.[a];n&&await r(a,"image"===a?E(n):new Blob([n],{type:"text/html;charset=utf-8"})).catch(()=>t=!0)}if(n.readiness&&(t||!e?.image&&!e?.html))return r("status",new Blob([`{"status":"${t?"failed":"skipped"}"}`],{type:"application/json"})).catch(()=>{})}),o}export{c as ATTRIBUTE_LIMIT,s as HTML_LIMIT,w as IMAGE_BYTES,e as OPT_OUT,r as PLACEHOLDER,h as TEXT_LIMIT,I as captureSnapshot,A as noteComment,m as skipped,R as snapshotFor,p as snapshotHtml,C as snapshotImage};
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare const IMAGE_BYTES = 250000;
|
|
|
28
28
|
export declare function snapshotImage(element: Element, options?: PaletteSnapshots): Promise<string>;
|
|
29
29
|
/** The server's snapshot support, from `/config`. */
|
|
30
30
|
export interface SnapshotLimits {
|
|
31
|
+
readiness?: boolean;
|
|
31
32
|
/** Whether guest sessions may attach snapshots. */
|
|
32
33
|
guests: boolean;
|
|
33
34
|
maxImageBytes: number;
|
|
@@ -44,6 +45,6 @@ export declare function captureSnapshot(element: Element | null, options: Palett
|
|
|
44
45
|
/**
|
|
45
46
|
* The snapshot of a draft's target, captured once per anchor: the composer reads it
|
|
46
47
|
* for a preview, and posting passes `post` to upload each kind as its raw file after
|
|
47
|
-
* the thread is saved. Never rejects;
|
|
48
|
+
* the thread is saved. Never rejects; failed uploads are reported when supported and the comment stays posted.
|
|
48
49
|
*/
|
|
49
|
-
export declare function snapshotFor<Anchor extends object>(anchor: Anchor, resolve: (anchor: Anchor) => Element | null, options: PaletteSnapshots, limits: SnapshotLimits, post?: (kind: "image" | "html", body: Blob) => Promise<unknown>): Promise<CapturedSnapshot | null>;
|
|
50
|
+
export declare function snapshotFor<Anchor extends object>(anchor: Anchor, resolve: (anchor: Anchor) => Element | null, options: PaletteSnapshots, limits: SnapshotLimits, post?: (kind: "image" | "html" | "status", body: Blob) => Promise<unknown>): Promise<CapturedSnapshot | null>;
|
package/package.json
CHANGED
package/server/index.ts
CHANGED
|
@@ -20,7 +20,8 @@ import { authOptions, type ProxyAuthProvider, type ProxyIdentity } from "./proxy
|
|
|
20
20
|
import { paletteEnv } from "./public-env";
|
|
21
21
|
import setupClient from "./setup-client.txt";
|
|
22
22
|
import {
|
|
23
|
-
|
|
23
|
+
enqueueWebhook,
|
|
24
|
+
drainWebhooks,
|
|
24
25
|
type WebhookEvent,
|
|
25
26
|
} from "./webhooks";
|
|
26
27
|
import {
|
|
@@ -202,14 +203,16 @@ async function body(request: Request): Promise<Record<string, unknown>> {
|
|
|
202
203
|
: path === "/me"
|
|
203
204
|
? ["name", "avatarUrl", "accentColor"]
|
|
204
205
|
: path === "/threads"
|
|
205
|
-
? ["body", "page", "anchor", "tags", "scope"]
|
|
206
|
+
? ["body", "page", "anchor", "tags", "scope", "snapshotIntent"]
|
|
206
207
|
: path.endsWith("/comments")
|
|
207
208
|
? ["body"]
|
|
208
209
|
: path.includes("/comments/")
|
|
209
210
|
? ["body"]
|
|
210
|
-
: path.
|
|
211
|
-
? ["
|
|
212
|
-
:
|
|
211
|
+
: path.endsWith("/snapshot")
|
|
212
|
+
? ["status"]
|
|
213
|
+
: path.startsWith("/threads/")
|
|
214
|
+
? ["anchor", "resolved", "tag", "untag", "scope"]
|
|
215
|
+
: [];
|
|
213
216
|
check(
|
|
214
217
|
Object.keys(result).every((key) => allowed.includes(key)),
|
|
215
218
|
400,
|
|
@@ -286,12 +289,10 @@ async function authenticate(
|
|
|
286
289
|
.get("Authorization")
|
|
287
290
|
?.match(/^Bearer (.{1,200})$/)?.[1];
|
|
288
291
|
check(bearer, 401, "Enter your name to comment.");
|
|
289
|
-
const { proxy, sessionMaxAge, allowGuests } = authOptions(env);
|
|
292
|
+
const { proxy, proxyAccessTokens, sessionMaxAge, allowGuests } = authOptions(env);
|
|
290
293
|
if (isPersonalToken(bearer)) {
|
|
291
|
-
// A token would outlive the proxy's own sign-in checks; agents exchange
|
|
292
|
-
// their proxy identity for a session instead.
|
|
293
294
|
check(
|
|
294
|
-
!proxy,
|
|
295
|
+
!proxy || proxyAccessTokens,
|
|
295
296
|
403,
|
|
296
297
|
"Access tokens are off behind the sign-in proxy. Use /auth/proxy/session."
|
|
297
298
|
);
|
|
@@ -1055,7 +1056,7 @@ async function route(
|
|
|
1055
1056
|
}
|
|
1056
1057
|
if (accountRoute) {
|
|
1057
1058
|
check(
|
|
1058
|
-
!auth.proxy,
|
|
1059
|
+
!auth.proxy || auth.proxyAccessTokens,
|
|
1059
1060
|
403,
|
|
1060
1061
|
"Access tokens are off behind the sign-in proxy. Use /auth/proxy/session."
|
|
1061
1062
|
);
|
|
@@ -1072,6 +1073,7 @@ async function route(
|
|
|
1072
1073
|
tokens: await listTokens(env, user.id),
|
|
1073
1074
|
projects: await accountProjects(env, user.id),
|
|
1074
1075
|
scopes: PAT_SCOPES,
|
|
1076
|
+
...(auth.proxy ? { policy: { explicitProjects: true, maxLifetimeDays: 365 } } : {}),
|
|
1075
1077
|
});
|
|
1076
1078
|
if (url.pathname === "/account/tokens" && request.method === "POST") {
|
|
1077
1079
|
await limit(env, `pat:create:${user.id}`, 10, 3600);
|
|
@@ -1147,11 +1149,10 @@ async function route(
|
|
|
1147
1149
|
!(await privateAccess(env, project)),
|
|
1148
1150
|
private: await privateAccess(env, project),
|
|
1149
1151
|
guestResolve: config.allowGuestResolve !== false,
|
|
1150
|
-
|
|
1151
|
-
// except behind a sign-in proxy.
|
|
1152
|
-
tokens: !auth.proxy,
|
|
1152
|
+
tokens: !auth.proxy || auth.proxyAccessTokens,
|
|
1153
1153
|
// Element snapshots are opt-in on the client; guests need project consent.
|
|
1154
1154
|
snapshots: {
|
|
1155
|
+
readiness: true,
|
|
1155
1156
|
guests: auth.allowGuests && config.allowGuestSnapshots === true,
|
|
1156
1157
|
maxImageBytes: SNAPSHOT_LIMITS.image,
|
|
1157
1158
|
maxHtmlBytes: SNAPSHOT_LIMITS.html,
|
|
@@ -1540,6 +1541,8 @@ async function route(
|
|
|
1540
1541
|
}
|
|
1541
1542
|
const id = crypto.randomUUID();
|
|
1542
1543
|
const firstCommentId = crypto.randomUUID();
|
|
1544
|
+
check(data.snapshotIntent === undefined || typeof data.snapshotIntent === "boolean", 400, "Invalid snapshot intent.");
|
|
1545
|
+
const snapshotStatus = data.snapshotIntent === undefined ? null : data.snapshotIntent ? "pending" : "skipped";
|
|
1543
1546
|
await env.DB.batch([
|
|
1544
1547
|
// Page-only threads rely on the column default, as before the scope column existed.
|
|
1545
1548
|
scope === "all"
|
|
@@ -1552,18 +1555,8 @@ async function route(
|
|
|
1552
1555
|
env.DB.prepare(
|
|
1553
1556
|
"INSERT INTO comments(id,thread_id,user_id,body,created_at) VALUES(?,?,?,?,?)"
|
|
1554
1557
|
).bind(firstCommentId, id, user.id, text, now),
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
project,
|
|
1558
|
-
user.id,
|
|
1559
|
-
config.retainedCommentsPerUser
|
|
1560
|
-
),
|
|
1561
|
-
]);
|
|
1562
|
-
ctx.waitUntil(
|
|
1563
|
-
dispatchWebhook(
|
|
1564
|
-
env,
|
|
1565
|
-
project,
|
|
1566
|
-
webhookEvent(
|
|
1558
|
+
env.DB.prepare("UPDATE threads SET snapshot_status=? WHERE id=?").bind(snapshotStatus, id),
|
|
1559
|
+
enqueueWebhook(env.DB, project, { ...webhookEvent(
|
|
1567
1560
|
"comment.created",
|
|
1568
1561
|
project,
|
|
1569
1562
|
repo,
|
|
@@ -1574,9 +1567,15 @@ async function route(
|
|
|
1574
1567
|
user.name,
|
|
1575
1568
|
text,
|
|
1576
1569
|
origin || url.origin
|
|
1577
|
-
)
|
|
1578
|
-
|
|
1579
|
-
|
|
1570
|
+
), ...(snapshotStatus ? { snapshotStatus } : {}) }, firstCommentId),
|
|
1571
|
+
...retainReviewerComments(
|
|
1572
|
+
env.DB,
|
|
1573
|
+
project,
|
|
1574
|
+
user.id,
|
|
1575
|
+
config.retainedCommentsPerUser
|
|
1576
|
+
),
|
|
1577
|
+
]);
|
|
1578
|
+
ctx.waitUntil(drainWebhooks(env));
|
|
1580
1579
|
return json({ id, commentId: firstCommentId }, 201);
|
|
1581
1580
|
}
|
|
1582
1581
|
const snapshotRoute = url.pathname.match(
|
|
@@ -1585,12 +1584,18 @@ async function route(
|
|
|
1585
1584
|
if (snapshotRoute) {
|
|
1586
1585
|
const [, threadId, pathKind] = snapshotRoute;
|
|
1587
1586
|
const thread = await env.DB.prepare(
|
|
1588
|
-
"SELECT id FROM threads WHERE id=? AND project=? AND repo=? AND branch=?"
|
|
1587
|
+
"SELECT id,snapshot_status,created_at FROM threads WHERE id=? AND project=? AND repo=? AND branch=?"
|
|
1589
1588
|
)
|
|
1590
1589
|
.bind(threadId, project, repo, branch)
|
|
1591
|
-
.first<{ id: string }>();
|
|
1590
|
+
.first<{ id: string; snapshot_status: string | null; created_at: number }>();
|
|
1592
1591
|
check(thread, 404, "Thread not found on this branch.");
|
|
1593
1592
|
if (request.method === "GET") {
|
|
1593
|
+
if (!pathKind) {
|
|
1594
|
+
const rows = await env.DB.prepare("SELECT kind FROM snapshots WHERE thread_id=?").bind(threadId).all<{kind:string}>();
|
|
1595
|
+
let status = rows.results.length ? "ready" : thread.snapshot_status ?? "unknown";
|
|
1596
|
+
if (status === "pending" && now - thread.created_at > 600000) status = "failed";
|
|
1597
|
+
return json({ status, kinds: rows.results.map(row => row.kind) });
|
|
1598
|
+
}
|
|
1594
1599
|
const kind = snapshotKind(pathKind);
|
|
1595
1600
|
const row = await env.DB.prepare(
|
|
1596
1601
|
"SELECT mime,bytes FROM snapshots WHERE thread_id=? AND kind=?"
|
|
@@ -1638,13 +1643,20 @@ async function route(
|
|
|
1638
1643
|
threadId,
|
|
1639
1644
|
kind
|
|
1640
1645
|
),
|
|
1641
|
-
env.DB.prepare("UPDATE threads SET updated_at=? WHERE id=?").bind(
|
|
1646
|
+
env.DB.prepare("UPDATE threads SET snapshot_status=CASE WHEN EXISTS (SELECT 1 FROM snapshots WHERE thread_id=?) THEN 'ready' ELSE 'skipped' END,updated_at=? WHERE id=?").bind(
|
|
1647
|
+
threadId,
|
|
1642
1648
|
now,
|
|
1643
1649
|
threadId
|
|
1644
1650
|
),
|
|
1645
1651
|
]);
|
|
1646
1652
|
return json({ ok: true, deleted: deleted.meta.changes });
|
|
1647
1653
|
}
|
|
1654
|
+
if (!pathKind && request.headers.get("Content-Type")?.startsWith("application/json")) {
|
|
1655
|
+
const data = await body(request);
|
|
1656
|
+
check(data.status === "skipped" || data.status === "failed", 400, "Report skipped or failed capture.");
|
|
1657
|
+
await env.DB.prepare("UPDATE threads SET snapshot_status=?,updated_at=? WHERE id=? AND snapshot_status='pending'").bind(data.status, now, threadId).run();
|
|
1658
|
+
return json({ ok: true });
|
|
1659
|
+
}
|
|
1648
1660
|
await limit(env, `${project}:snapshot:${user.id}`, 60, 600);
|
|
1649
1661
|
const snapshot = await readSnapshot(
|
|
1650
1662
|
request,
|
|
@@ -1660,8 +1672,9 @@ async function route(
|
|
|
1660
1672
|
snapshotCap(config.maxSnapshotBytes),
|
|
1661
1673
|
true
|
|
1662
1674
|
),
|
|
1663
|
-
env.DB.prepare("UPDATE threads SET updated_at=? WHERE id=?").bind(
|
|
1675
|
+
env.DB.prepare("UPDATE threads SET snapshot_status='ready',updated_at=? WHERE id=? AND EXISTS (SELECT 1 FROM snapshots WHERE thread_id=?)").bind(
|
|
1664
1676
|
now,
|
|
1677
|
+
threadId,
|
|
1665
1678
|
threadId
|
|
1666
1679
|
),
|
|
1667
1680
|
]);
|
|
@@ -1777,35 +1790,20 @@ async function route(
|
|
|
1777
1790
|
);
|
|
1778
1791
|
check(typeof data.resolved === "boolean", 400, "Invalid resolved state.");
|
|
1779
1792
|
const becameResolved = !thread.resolved && data.resolved;
|
|
1780
|
-
|
|
1781
|
-
"
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
project,
|
|
1795
|
-
webhookEvent(
|
|
1796
|
-
"comment.resolved",
|
|
1797
|
-
project,
|
|
1798
|
-
repo,
|
|
1799
|
-
branch,
|
|
1800
|
-
threadId,
|
|
1801
|
-
thread.page,
|
|
1802
|
-
thread.anchor,
|
|
1803
|
-
user.name,
|
|
1804
|
-
null,
|
|
1805
|
-
origin || url.origin
|
|
1806
|
-
)
|
|
1807
|
-
).catch(() => {})
|
|
1808
|
-
);
|
|
1793
|
+
const firstComment = becameResolved
|
|
1794
|
+
? await env.DB.prepare("SELECT id FROM comments WHERE thread_id=? ORDER BY created_at,id LIMIT 1")
|
|
1795
|
+
.bind(threadId).first<{ id: string }>()
|
|
1796
|
+
: null;
|
|
1797
|
+
await env.DB.batch([
|
|
1798
|
+
env.DB.prepare(
|
|
1799
|
+
"UPDATE threads SET resolved=?,resolved_by=?,updated_at=? WHERE id=?"
|
|
1800
|
+
).bind(Number(data.resolved), data.resolved ? user.id : null, now, threadId),
|
|
1801
|
+
...(firstComment ? [enqueueWebhook(env.DB, project, webhookEvent(
|
|
1802
|
+
"comment.resolved", project, repo, branch, threadId, thread.page,
|
|
1803
|
+
thread.anchor, user.name, null, origin || url.origin
|
|
1804
|
+
), firstComment.id)] : []),
|
|
1805
|
+
]);
|
|
1806
|
+
ctx.waitUntil(drainWebhooks(env));
|
|
1809
1807
|
return json({ ok: true });
|
|
1810
1808
|
}
|
|
1811
1809
|
if (
|
|
@@ -1823,6 +1821,18 @@ async function route(
|
|
|
1823
1821
|
now,
|
|
1824
1822
|
threadId
|
|
1825
1823
|
),
|
|
1824
|
+
enqueueWebhook(env.DB, project, webhookEvent(
|
|
1825
|
+
"comment.replied",
|
|
1826
|
+
project,
|
|
1827
|
+
repo,
|
|
1828
|
+
branch,
|
|
1829
|
+
threadId,
|
|
1830
|
+
thread.page,
|
|
1831
|
+
thread.anchor,
|
|
1832
|
+
user.name,
|
|
1833
|
+
text,
|
|
1834
|
+
origin || url.origin
|
|
1835
|
+
), replyId),
|
|
1826
1836
|
...retainReviewerComments(
|
|
1827
1837
|
env.DB,
|
|
1828
1838
|
project,
|
|
@@ -1835,24 +1845,7 @@ async function route(
|
|
|
1835
1845
|
409,
|
|
1836
1846
|
"This thread has reached 200 comments. Start another thread."
|
|
1837
1847
|
);
|
|
1838
|
-
ctx.waitUntil(
|
|
1839
|
-
dispatchWebhook(
|
|
1840
|
-
env,
|
|
1841
|
-
project,
|
|
1842
|
-
webhookEvent(
|
|
1843
|
-
"comment.replied",
|
|
1844
|
-
project,
|
|
1845
|
-
repo,
|
|
1846
|
-
branch,
|
|
1847
|
-
threadId,
|
|
1848
|
-
thread.page,
|
|
1849
|
-
thread.anchor,
|
|
1850
|
-
user.name,
|
|
1851
|
-
text,
|
|
1852
|
-
origin || url.origin
|
|
1853
|
-
)
|
|
1854
|
-
).catch(() => {})
|
|
1855
|
-
);
|
|
1848
|
+
ctx.waitUntil(drainWebhooks(env));
|
|
1856
1849
|
return json({ ok: true, id: replyId }, 201);
|
|
1857
1850
|
}
|
|
1858
1851
|
check(commentId, 405, "Method not allowed.");
|
|
@@ -1957,6 +1950,7 @@ const settingsFor = new WeakMap<Env, Env>();
|
|
|
1957
1950
|
export default {
|
|
1958
1951
|
async scheduled(_event, env, ctx) {
|
|
1959
1952
|
ctx.waitUntil(maintain(env.DB, true));
|
|
1953
|
+
ctx.waitUntil(drainWebhooks(paletteEnv(env)));
|
|
1960
1954
|
},
|
|
1961
1955
|
async fetch(request, settings, ctx) {
|
|
1962
1956
|
// Deployments configured before the rename set KOMO_ names. One copy per
|
|
@@ -2047,3 +2041,5 @@ export default {
|
|
|
2047
2041
|
return new Response(response.body, { status: response.status, headers });
|
|
2048
2042
|
},
|
|
2049
2043
|
} satisfies ExportedHandler<Env>;
|
|
2044
|
+
|
|
2045
|
+
export { drainWebhooks } from "./webhooks";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
CREATE TABLE webhook_outbox (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
project TEXT NOT NULL,
|
|
4
|
+
body TEXT NOT NULL,
|
|
5
|
+
url TEXT NOT NULL,
|
|
6
|
+
secret TEXT NOT NULL,
|
|
7
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
8
|
+
next_attempt BIGINT NOT NULL,
|
|
9
|
+
claim_until BIGINT NOT NULL DEFAULT 0,
|
|
10
|
+
claim_token TEXT NOT NULL DEFAULT '',
|
|
11
|
+
state TEXT NOT NULL DEFAULT 'pending'
|
|
12
|
+
);
|
|
13
|
+
CREATE INDEX webhook_outbox_due ON webhook_outbox(state,next_attempt,claim_until);
|
|
14
|
+
ALTER TABLE threads ADD COLUMN snapshot_status TEXT;
|
package/server/node-webhooks.ts
CHANGED
|
@@ -101,6 +101,11 @@ export async function sendWebhookPinned(
|
|
|
101
101
|
lookup: pinnedLookup(address),
|
|
102
102
|
},
|
|
103
103
|
(response) => {
|
|
104
|
+
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) {
|
|
105
|
+
response.destroy();
|
|
106
|
+
finish(new Error("Webhook returned a non-success status."));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
104
109
|
// Read and discard a bounded amount. Never follow redirects or log bodies.
|
|
105
110
|
let read = 0;
|
|
106
111
|
response.on("data", (chunk: Buffer) => {
|
package/server/proxy-auth.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type AuthOptions = {
|
|
|
27
27
|
signInPrefix: string;
|
|
28
28
|
requireSessionForReads: boolean;
|
|
29
29
|
allowGuests: boolean;
|
|
30
|
+
proxyAccessTokens: boolean;
|
|
30
31
|
/** Session lifetime cap in milliseconds; undefined keeps the default. */
|
|
31
32
|
sessionMaxAge?: number;
|
|
32
33
|
};
|
|
@@ -95,6 +96,7 @@ function resolve(env: OptionEnv): AuthOptions {
|
|
|
95
96
|
const allowGuests = flag(env, "PALETTE_ALLOW_GUESTS", true);
|
|
96
97
|
return {
|
|
97
98
|
proxy,
|
|
99
|
+
proxyAccessTokens: flag(env, "PALETTE_PROXY_ACCESS_TOKENS", false),
|
|
98
100
|
signInPrefix,
|
|
99
101
|
requireSessionForReads: !!proxy || requireSessionForReads,
|
|
100
102
|
allowGuests: !proxy && allowGuests,
|
package/server/public-env.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface ServerEnv {
|
|
|
20
20
|
PALETTE_PAUSED?: string;
|
|
21
21
|
/** Trusted identity proxy that signs reviewers in, e.g. "gcp-iap". Off unless set. */
|
|
22
22
|
PALETTE_PROXY_AUTH?: string;
|
|
23
|
+
/** Explicitly allow bounded, project-scoped access tokens behind a proxy. */
|
|
24
|
+
PALETTE_PROXY_ACCESS_TOKENS?: string;
|
|
23
25
|
/** The audience the proxy signs its assertions for. */
|
|
24
26
|
PALETTE_PROXY_AUDIENCE?: string;
|
|
25
27
|
/** Older name: setting it alone turns on "gcp-iap" with this audience. */
|
|
@@ -53,6 +55,8 @@ export interface ServerEnv {
|
|
|
53
55
|
KOMO_PAUSED?: string;
|
|
54
56
|
/** @deprecated Read when PALETTE_PROXY_AUTH is unset. */
|
|
55
57
|
KOMO_PROXY_AUTH?: string;
|
|
58
|
+
/** @deprecated Read when PALETTE_PROXY_ACCESS_TOKENS is unset. */
|
|
59
|
+
KOMO_PROXY_ACCESS_TOKENS?: string;
|
|
56
60
|
/** @deprecated Read when PALETTE_PROXY_AUDIENCE is unset. */
|
|
57
61
|
KOMO_PROXY_AUDIENCE?: string;
|
|
58
62
|
/** @deprecated Read when PALETTE_IAP_AUDIENCE is unset. */
|