Merge pull request #878 from tdurieux/chore/dependency-upgrades

chore(deps): upgrade dependencies and remove production vulnerabilities
This commit is contained in:
Thomas Durieux authored and GitHub committed 2026-10-08 12:28:30 +02:00
commit 079bc8a762
29 files changed
+5220 -21129

No files matched your search

+2 -1
View File
@@ -28,5 +28,6 @@ jobs:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build:ui
- run: npm run build
- run: npm audit --omit=dev --audit-level=moderate
- run: npm test
+4
View File
@@ -44,6 +44,10 @@ anonymous_github
## Self-hosting
Local installation requires Node.js 22.15 or newer in the 22.x series,
24.x, or 26 or newer. Run `npm ci` and `npm run build` to install and build.
`npm run dev` uses Node's watch mode; `npm run build:ui` rebuilds browser assets.
<details>
<summary>Run your own instance with Docker</summary>
+26 -6
View File
@@ -1,4 +1,4 @@
const { src, dest, parallel, series } = require("gulp");
const { src, dest } = require("vinyl-fs");
const uglify = require("gulp-uglify");
const concat = require("gulp-concat");
const order = require("ordered-read-streams");
@@ -6,6 +6,7 @@ const { pipeline } = require("node:stream");
const cleanCss = require("gulp-clean-css");
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const esbuild = require("esbuild");
const { compileTemplate } = require("vue/compiler-sfc");
const { Script } = require("node:vm");
@@ -55,10 +56,14 @@ function hashFile(filePath) {
return crypto.createHash("md5").update(content).digest("hex").slice(0, 10);
}
// Gulp 5 does not preserve array order. Read each asset in its declared order
// Read each asset in its declared order
// so libraries precede their plugins and application code, and CSS keeps its cascade.
function orderedSrc(files) {
return order(files.map(file => src(file)));
return order(files.map(assetSrc));
}
function assetSrc(file) {
return src(path.basename(file), { cwd: path.dirname(file) });
}
function buildCoreJs(cb) {
@@ -98,7 +103,7 @@ async function buildVendorJs() {
}
function buildMermaidJs(cb) {
pipeline(src(mermaidFiles), concat("mermaid.min.js"), dest("public/script"), cb);
pipeline(orderedSrc(mermaidFiles), concat("mermaid.min.js"), dest("public/script"), cb);
}
function buildCss(cb) {
@@ -129,6 +134,21 @@ function writeManifest(cb) {
cb();
}
const buildAssets = parallel(buildCoreJs, buildVendorJs, buildMermaidJs, buildCss);
async function buildAssets() {
await Promise.all([
promisify(buildCoreJs)(), buildVendorJs(),
promisify(buildMermaidJs)(), promisify(buildCss)(),
]);
await promisify(writeManifest)();
}
exports.default = series(buildAssets, writeManifest);
exports.default = cb => buildAssets().then(() => cb(), cb);
if (require.main === module) {
exports.default(error => {
if (error) {
console.error(error);
process.exitCode = 1;
}
});
}
+4766 -20902
View File
File diff suppressed because it is too large. Load diff
+51 -60
View File
@@ -11,17 +11,17 @@
"coverage:lcov": "c8 --all --reporter=lcov --include='src/core/**/*.ts' --include='src/server/routes/**/*.ts' --include='src/config.ts' --exclude='src/**/*.d.ts' --exclude='src/core/model/**' npm test",
"lint": "eslint .",
"start": "node --inspect=5858 -r ts-node/register ./src/server/index.ts",
"dev": "nodemon --transpile-only ./src/server/index.ts",
"dev": "node --watch -r ts-node/register ./src/server/index.ts",
"dev:ui": "node scripts/dev-proxy.js",
"build": "rm -rf build && tsc && gulp",
"build": "rm -rf build && tsc && node gulpfile.js",
"knip": "knip",
"migrate:credentials": "node -r ts-node/register src/scripts/migrate-credentials.ts",
"recover:owners": "node -r ts-node/register src/scripts/recover-repository-owners.ts",
"migrate:performance": "node -r ts-node/register src/scripts/create-performance-indexes.ts",
"test:performance": "mocha test/performance-*.test.js",
"benchmark:performance": "node scripts/performance-benchmark.js",
"build:ui": "gulp",
"test:ui": "gulp && mocha test/frontend-regressions.test.js test/dashboard-ui.test.js test/vue-ui.test.js"
"build:ui": "node gulpfile.js",
"test:ui": "npm run build:ui && mocha test/frontend-regressions.test.js test/dashboard-ui.test.js test/vue-ui.test.js"
},
"repository": {
"type": "git",
@@ -41,85 +41,76 @@
"build"
],
"dependencies": {
"@aws-sdk/client-s3": "^3.540.0",
"@aws-sdk/lib-storage": "^3.540.0",
"@mongodb-js/zstd": "^1.2.0",
"@aws-sdk/client-s3": "^3.1147.0",
"@aws-sdk/lib-storage": "^3.1147.0",
"@mongodb-js/zstd": "^1.2.2",
"@octokit/plugin-throttling": "^8.2.0",
"@octokit/rest": "^20.0.2",
"@smithy/node-http-handler": "^2.5.0",
"archiver": "^5.3.2",
"bullmq": "^5.76.5",
"cacheable-lookup": "^5.0.3",
"compression": "^1.7.4",
"connect-redis": "^7.0.1",
"@octokit/rest": "^20.1.2",
"@smithy/node-http-handler": "^4.12.1",
"archiver": "^7.0.1",
"bullmq": "^5.81.5",
"cacheable-lookup": "^6.1.0",
"compression": "^1.8.2",
"connect-redis": "^10.0.0",
"crypto-js": "^4.2.0",
"decompress-stream-to-s3": "^2.1.1",
"dotenv": "^16.4.5",
"dotenv": "^18.0.6",
"express": "^5.2.1",
"express-rate-limit": "^6.11.2",
"express-session": "^1.18.0",
"express-slow-down": "^2.0.1",
"express-rate-limit": "^8.7.1",
"express-session": "^1.19.0",
"express-slow-down": "^3.1.1",
"got": "^11.8.6",
"inquirer": "^8.2.6",
"isbinaryfile": "^5.0.4",
"marked": "^5.1.2",
"mime-types": "^2.1.35",
"mongoose": "^7.6.10",
"inquirer": "^8.2.7",
"isbinaryfile": "^5.0.7",
"marked": "^15.0.12",
"mime-types": "^3.0.2",
"mongoose": "^7.8.12",
"node-schedule": "^2.1.1",
"parse-github-url": "^1.0.2",
"passport": "^0.6.0",
"parse-github-url": "^1.0.4",
"passport": "^0.7.0",
"passport-github2": "^0.1.12",
"rate-limit-redis": "^4.2.0",
"rate-limit-redis": "^6.0.1",
"re2js": "^2.8.6",
"redis": "^4.6.13",
"sanitize-html": "^2.17.7",
"redis": "^5.12.1",
"sanitize-html": "^2.18.0",
"ts-custom-error": "^3.3.1",
"unzip-stream": "^0.3.1"
"unzip-stream": "^0.3.4"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/archiver": "^5.3.4",
"@types/compression": "^1.7.5",
"@types/archiver": "^7.0.0",
"@types/compression": "^1.8.1",
"@types/crypto-js": "^4.2.2",
"@types/express": "^5.0.6",
"@types/express-session": "^1.18.0",
"@types/got": "^9.6.12",
"@types/inquirer": "^8.2.10",
"@types/marked": "^5.0.2",
"@types/mime-types": "^2.1.4",
"@types/node-schedule": "^2.1.6",
"@types/express-session": "^1.19.0",
"@types/inquirer": "^8.2.13",
"@types/mime-types": "^3.0.1",
"@types/node-schedule": "^2.1.8",
"@types/parse-github-url": "^1.0.3",
"@types/passport": "^1.0.16",
"@types/passport": "^1.0.17",
"@types/passport-github2": "^1.2.9",
"@types/sanitize-html": "^2.16.1",
"@types/sanitize-html": "^2.16.2",
"@types/unzip-stream": "^0.3.4",
"c8": "^10.1.3",
"c8": "^12.0.0",
"chai": "^4.5.0",
"esbuild": "^0.25.12",
"eslint": "^10.2.0",
"gulp": "^5.0.0",
"esbuild": "^0.28.2",
"eslint": "^10.12.0",
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.6.1",
"gulp-uglify": "^3.0.2",
"http-proxy-middleware": "^3.0.5",
"jsdom": "^26.1.0",
"knip": "^5.1.0",
"mocha": "^12.0.0",
"nodemon": "^3.1.0",
"http-proxy-middleware": "^4.2.0",
"jsdom": "^29.1.1",
"knip": "^6.40.0",
"mocha": "^12.0.3",
"ordered-read-streams": "^2.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.3",
"typescript-eslint": "^8.58.2",
"vue": "^3.5.42",
"vue-router": "^4.6.4"
"typescript": "^5.9.3",
"typescript-eslint": "^8.71.1",
"vue": "^3.5.43",
"vue-router": "^4.6.4",
"vinyl-fs": "^4.0.2"
},
"nodemonConfig": {
"ignore": [
"public",
"test",
"repo",
"repositories",
"session-store"
]
"engines": {
"node": "^22.15.0 || ^24.0.0 || >=26.0.0"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"core.min.js": "core.3b07189932.min.js",
"vendor.min.js": "vendor.ba03f7ecf6.min.js",
"vendor.min.js": "vendor.9d3752006f.min.js",
"mermaid.min.js": "mermaid.f848a72d16.min.js",
"all.min.css": "all.d1feb1dfc4.min.css",
"markdown.min.js": "markdown.44f2e93820.min.js",
+26 -26
View File
@@ -1,63 +1,63 @@
"use strict";(()=>{var sd=Object.defineProperty;var nd=(e,t)=>{for(var s in t)sd(e,s,{get:t[s],enumerable:!0})};var he=["abap","abc","actionscript","ada","alda","apache_conf","apex","applescript","aql","asciidoc","asl","assembly_x86","autohotkey","batchfile","c9search","c_cpp","cirru","clojure","cobol","coffee","coldfusion","crystal","csharp","csound_document","csound_orchestra","csound_score","csp","css","curly","d","dart","diff","django","dockerfile","dot","drools","edifact","eiffel","ejs","elixir","elm","erlang","forth","fortran","fsharp","fsl","ftl","gcode","gherkin","gitignore","glsl","gobstones","golang","graphqlschema","groovy","haml","handlebars","haskell","haskell_cabal","haxe","hjson","html","html_elixir","html_ruby","ini","io","jack","jade","java","javascript","json","json5","jsoniq","jsp","jssm","jsx","julia","kotlin","latex","less","liquid","lisp","livescript","logiql","logtalk","lsl","lua","luapage","lucene","makefile","markdown","mask","matlab","maze","mediawiki","mel","mixal","mushcode","mysql","nginx","nim","nix","nsis","nunjucks","objectivec","ocaml","pascal","perl","perl6","pgsql","php","php_laravel_blade","pig","plain_text","powershell","praat","prisma","prolog","properties","protobuf","puppet","python","qml","r","razor","rdoc","red","redshift","rhtml","rst","ruby","rust","sass","scad","scala","scheme","scss","sh","sjs","slim","smarty","snippets","soy_template","space","sparql","sql","sqlserver","stylus","svg","swift","tcl","terraform","tex","text","textile","toml","tsx","turtle","twig","typescript","vala","vbscript","velocity","verilog","vhdl","visualforce","wollok","xml","xquery","yaml","zeek"];var ve={notebook:"/script/notebook.c3144c18ae.min.js",org:"/script/org.f61f262915.min.js",pdf:"/script/pdf.4d9cc6ff49.min.js",markdown:"/script/markdown.44f2e93820.min.js",editor:"/script/editor.4f142d3adc.min.js"};var cs=new Map,ad=ve;function Xt(e){if(!cs.has(e)){let t=document.createElement("script");t.src=ad[e];let s=new Promise((r,n)=>{t.onload=r,t.onerror=()=>{cs.delete(e),t.remove(),n(new Error(`Unable to load ${e}. Please retry.`))}});cs.set(e,s),document.head.appendChild(t)}return cs.get(e)}async function ur(){await Xt("editor"),ace.config.set("basePath","/script/external/ace/")}function ho(e){let t=Object.create(null);for(let s of e.split(","))t[s]=1;return s=>s in t}var Pe={},Zt=[],at=()=>{},rn=()=>!1,vo=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),go=e=>e.startsWith("onUpdate:"),Ge=Object.assign,Po=(e,t)=>{let s=e.indexOf(t);s>-1&&e.splice(s,1)},ld=Object.prototype.hasOwnProperty,De=(e,t)=>ld.call(e,t),ye=Array.isArray,Lt=e=>Vo(e)==="[object Map]",Ut=e=>Vo(e)==="[object Set]",cr=e=>Vo(e)==="[object Date]";var we=e=>typeof e=="function",ze=e=>typeof e=="string",mt=e=>typeof e=="symbol",Te=e=>e!==null&&typeof e=="object",an=e=>(Te(e)||we(e))&&we(e.then)&&we(e.catch),mr=Object.prototype.toString,Vo=e=>mr.call(e),ln=e=>Vo(e).slice(8,-1),ps=e=>Vo(e)==="[object Object]",fs=e=>ze(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,yo=ho(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted");var ms=e=>{let t=Object.create(null);return(s=>t[s]||(t[s]=e(s)))},dd=/-\w/g,Je=ms(e=>e.replace(dd,t=>t.slice(1).toUpperCase())),ud=/\B([A-Z])/g,St=ms(e=>e.replace(ud,"-$1").toLowerCase()),xt=ms(e=>e.charAt(0).toUpperCase()+e.slice(1)),qo=ms(e=>e?`on${xt(e)}`:""),gt=(e,t)=>!Object.is(e,t),bo=(e,...t)=>{for(let s=0;s<e.length;s++)e[s](...t)},hs=(e,t,s,r=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:s})},wo=e=>{let t=parseFloat(e);return isNaN(t)?e:t};var pr,Mo=()=>pr||(pr=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Me(e){if(ye(e)){let t={};for(let s=0;s<e.length;s++){let r=e[s],n=ze(r)?md(r):Me(r);if(n)for(let i in n)t[i]=n[i]}return t}else if(ze(e)||Te(e))return e}var cd=/;(?![^(]*\))/g,pd=/:([^]+)/,fd=/\/\*[^]*?\*\//g;function md(e){let t={};return e.replace(fd,"").split(cd).forEach(s=>{if(s){let r=s.split(pd);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function T(e){let t="";if(ze(e))t=e;else if(ye(e))for(let s=0;s<e.length;s++){let r=T(e[s]);r&&(t+=r+" ")}else if(Te(e))for(let s in e)e[s]&&(t+=s+" ");return t.trim()}var hr="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",vr=ho(hr),hd=ho(hr+",async,autofocus,autoplay,controls,default,defer,disabled,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");function vs(e){return!!e||e===""}function vd(e,t){if(e.length!==t.length)return!1;let s=!0;for(let r=0;s&&r<e.length;r++)s=Nt(e[r],t[r]);return s}function fr(e,t){if(e.size!==t.size)return!1;let s=Array.from(t),r=new Uint8Array(s.length);for(let n of e){let i=-1;for(let a=0;a<s.length;a++)if(!r[a]&&Nt(n,s[a])){i=a;break}if(i<0)return!1;r[i]=1}return!0}function Nt(e,t){if(e===t)return!0;let s=cr(e),r=cr(t);if(s||r)returLine truncated
`))),e[uo]=Ws(n);let i=r||n.props&&n.props.type==="number";lo(e,t?"change":"input",a=>{a.target.composing||e[uo](Hn(e.value,s,i))}),(s||i)&&lo(e,"change",()=>{e.value=Hn(e.value,s,i)}),t||(lo(e,"compositionstart",ic),lo(e,"compositionend",Ki),lo(e,"change",Ki))},mounted(e,{value:t,modifiers:{trim:s,number:r}}){let n=t??"",i=e[js];delete e[js],i!==void 0&&(e.type==="text"||e.type==="textarea")&&e.value!==i?e[uo](Hn(e.value,s,r)):e.value=n},beforeUpdate(e,{value:t,oldValue:s,modifiers:{lazy:r,trim:n,number:i}},a){if(e[uo]=Ws(a),e.composing)return;let u=(i||e.type==="number")&&!/^0\d/.test(e.value)?wo(e.value):e.value,f=t??"";if(u===f)return;let h=e.getRootNode();(h instanceof Document||h instanceof ShadowRoot)&&h.activeElement===e&&e.type!=="range"&&(r&&t===s||n&&e.value.trim()===f)||(e.value=f)}};var xi={deep:!0,created(e,{value:t,modifiers:{number:s}},r){e._modelValue=t,lo(e,"change",()=>{let n=Array.prototype.filter.call(e.options,f=>f.selected).map(f=>s?wo(Ks(f)):Ks(f)),i=e.multiple,a=i?Ut(e._modelValue)?new Set(n):n:n[0],u=e._pendingValue=[i,i?ye(a)?n.slice():n:a];try{e[uo](a)}finally{st(()=>{e._pendingValue===u&&(e._pendingValue=void 0)})}}),e[uo]=Ws(r)},mounted(e,{value:t}){Yi(e,t)},beforeUpdate(e,{value:t},s){e._modelValue=t,e[uo]=Ws(s)},updated(e,{value:t}){let s=e._pendingValue;e._pendingValue=void 0,(!s||s[0]!==e.multiple||!ac(t,s[1],s[0]))&&Yi(e,t)}};function ac(e,t,s){if(!s)return Nt(e,t);if(ye(e))return Nt(e,t);if(Ut(e)){if(e.size!==t.length)return!1;for(let r of t)if(!e.has(r))return!1;return!0}return!1}function Yi(e,t){let s=e.multiple,r=ye(t);if(!(s&&!r&&!Ut(t))){for(let n=0,i=e.options.length;n<i;n++){let a=e.options[n],u=Ks(a);if(s)if(r){let f=typeof u;f==="string"||f==="number"?a.selected=t.some(h=>String(h)===String(u)):a.selected=gr(t,u)>-1}else a.selected=t.has(u);else if(Nt(Ks(a),t)){e.selectedIndex!==n&&(e.selectedIndex=n);return}}!s&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function Ks(e){return"_value"in e?e._value:e.value}var lc=["ctrl","shift","alt","meta"],dc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>lc.some(s=>e[`${s}Key`]&&!t.includes(s))},me=(e,t)=>{if(!e)return e;let s=e._withMods||(e._withMods={}),r=t.join(".");return s[r]||(s[r]=((n,...i)=>{for(let a=0;a<t.length;a++){let u=dc[t[a]];if(u&&u(n,t))return}return e(n,...i)}))},uc={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},Ro=(e,t)=>{let s=e._withKeys||(e._withKeys={}),r=t.join(".");return s[r]||(s[r]=(n=>{if(!("key"in n))return;let i=St(n.key);if(t.some(a=>a===i||uc[a]===i))return e(n)}))},cc=Ge({patchProp:sc},zu),Ji;function pc(){return Ji||(Ji=Ti(cc))}var ea=((...e)=>{let t=pc().createApp(...e),{mount:s}=t;return t.mount=r=>{let n=mc(r);if(!n)return;let i=t._component;!we(i)&&!i.render&&!i.template&&(i.template=n.innerHTML),n.nodeType===1&&(n.textContent="");let a=s(n,!1,fc(n));return n instanceof Element&&(n.removeAttribute("v-cloak"),n.setAttribute("data-v-app","")),a},t});function fc(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function mc(e){return ze(e)?document.querySelector(e):e}var fo=typeof document<"u";function oa(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function hc(e){return e.__esModule||e[Symbol.toStringTag]==="Module"||e.default&&oa(e.default)}var Oe=Object.assign;function Ys(e,t){let s={};for(let r in t){let n=t[r];s[r]=wt(n)?n.map(e):e(n)}return s}var Ao=()=>{},wt=Array.isArray;function Wn(e,t){let s={};for(let r in e)s[r]=r in t?t[r]:e[r];return s}var sa=/#/g,vc=/&/g,gc=/\//g,yc=/=/g,bc=/\?/g,na=/\+/g,wc=/%5B/g,kc=/%5D/g,ra=/%5E/g,Ec=/%60/g,ia=/%7B/g,Cc=/%7C/g,aa=/%7D/g,_c=/%20/g;function Kn(e){return e==null?"":encodeURI(""+e).replace(Cc,"|").replace(wc,"[").replace(kc,"]")}function la(e){return Kn(e).replace(ia,"{").replace(aa,"}").replace(ra,"^")}function jn(e){return Kn(e).replace(na,"%2B").replace(_c,"+").replace(sa,"%23").replace(vc,"%26").replace(Ec,"`").replace(ia,"{").replace(aa,"}").replace(ra,"^")}function Sc(e){return jn(e).replace(yc,"%3D")}function Nc(e){return Kn(e).replace(sa,"%23").replace(bc,"%3F")}function da(e){return Nc(e).replace(gc,"%2F")}function To(e){if(e==null)return null;try{return decodeURIComponent(""+e)}catch{}return""+e}var Dc=/\/$/,Rc=e=>e.replace(Dc,"");function Js(e,t,s="/"){let r,n={},i="",a="",u=t.indexOf("#"),f=t.indexOf("?");return f=u>=0&&f>u?-1:f,f>=0&&(r=t.slice(0,f),i=t.slice(f,u>0?u:t.length),n=e(i.slice(1))),u>=0&&(r=r||t.slice(0,u),a=t.slice(u,t.length)),r=Ac(r??t,s),{fullPath:r+i+a,path:r,query:n,hash:To(a)}}function ua(e,t){let s=t.query?e(t.query):"";return t.path+(s&&"?")+s+(t.hash||"")}function Yn(e,t){return!t||!e.toLowerCase().sLine truncated
"use strict";(()=>{var nd=Object.defineProperty;var rd=(e,t)=>{for(var n in t)nd(e,n,{get:t[n],enumerable:!0})};var he=["abap","abc","actionscript","ada","alda","apache_conf","apex","applescript","aql","asciidoc","asl","assembly_x86","autohotkey","batchfile","c9search","c_cpp","cirru","clojure","cobol","coffee","coldfusion","crystal","csharp","csound_document","csound_orchestra","csound_score","csp","css","curly","d","dart","diff","django","dockerfile","dot","drools","edifact","eiffel","ejs","elixir","elm","erlang","forth","fortran","fsharp","fsl","ftl","gcode","gherkin","gitignore","glsl","gobstones","golang","graphqlschema","groovy","haml","handlebars","haskell","haskell_cabal","haxe","hjson","html","html_elixir","html_ruby","ini","io","jack","jade","java","javascript","json","json5","jsoniq","jsp","jssm","jsx","julia","kotlin","latex","less","liquid","lisp","livescript","logiql","logtalk","lsl","lua","luapage","lucene","makefile","markdown","mask","matlab","maze","mediawiki","mel","mixal","mushcode","mysql","nginx","nim","nix","nsis","nunjucks","objectivec","ocaml","pascal","perl","perl6","pgsql","php","php_laravel_blade","pig","plain_text","powershell","praat","prisma","prolog","properties","protobuf","puppet","python","qml","r","razor","rdoc","red","redshift","rhtml","rst","ruby","rust","sass","scad","scala","scheme","scss","sh","sjs","slim","smarty","snippets","soy_template","space","sparql","sql","sqlserver","stylus","svg","swift","tcl","terraform","tex","text","textile","toml","tsx","turtle","twig","typescript","vala","vbscript","velocity","verilog","vhdl","visualforce","wollok","xml","xquery","yaml","zeek"];var ve={notebook:"/script/notebook.c3144c18ae.min.js",org:"/script/org.f61f262915.min.js",markdown:"/script/markdown.44f2e93820.min.js",pdf:"/script/pdf.4d9cc6ff49.min.js",editor:"/script/editor.4f142d3adc.min.js"};var cs=new Map,ld=ve;function xt(e){if(!cs.has(e)){let t=document.createElement("script");t.src=ld[e];let n=new Promise((r,s)=>{t.onload=r,t.onerror=()=>{cs.delete(e),t.remove(),s(new Error(`Unable to load ${e}. Please retry.`))}});cs.set(e,n),document.head.appendChild(t)}return cs.get(e)}async function ur(){await xt("editor"),ace.config.set("basePath","/script/external/ace/")}function ho(e){let t=Object.create(null);for(let n of e.split(","))t[n]=1;return n=>n in t}var Ve={},Xt=[],at=()=>{},rn=()=>!1,vo=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),go=e=>e.startsWith("onUpdate:"),Ge=Object.assign,Po=(e,t)=>{let n=e.indexOf(t);n>-1&&e.splice(n,1)},dd=Object.prototype.hasOwnProperty,De=(e,t)=>dd.call(e,t),ye=Array.isArray,Ut=e=>Vo(e)==="[object Map]",Ft=e=>Vo(e)==="[object Set]",cr=e=>Vo(e)==="[object Date]";var we=e=>typeof e=="function",ze=e=>typeof e=="string",mt=e=>typeof e=="symbol",Te=e=>e!==null&&typeof e=="object",an=e=>(Te(e)||we(e))&&we(e.then)&&we(e.catch),hr=Object.prototype.toString,Vo=e=>hr.call(e),ln=e=>Vo(e).slice(8,-1),ps=e=>Vo(e)==="[object Object]",fs=e=>ze(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,yo=ho(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted");var ms=e=>{let t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))},ud=/-\w/g,Je=ms(e=>e.replace(ud,t=>t.slice(1).toUpperCase())),cd=/\B([A-Z])/g,St=ms(e=>e.replace(cd,"-$1").toLowerCase()),eo=ms(e=>e.charAt(0).toUpperCase()+e.slice(1)),qo=ms(e=>e?`on${eo(e)}`:""),yt=(e,t)=>!Object.is(e,t),bo=(e,...t)=>{for(let n=0;n<e.length;n++)e[n](...t)},hs=(e,t,n,r=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},wo=e=>{let t=parseFloat(e);return isNaN(t)?e:t};var pr,Mo=()=>pr||(pr=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Me(e){if(ye(e)){let t={};for(let n=0;n<e.length;n++){let r=e[n],s=ze(r)?hd(r):Me(r);if(s)for(let i in s)t[i]=s[i]}return t}else if(ze(e)||Te(e))return e}var pd=/;(?![^(]*\))/g,fd=/:([^]+)/,md=/"(?:[^"\\]|\\[^])*"|'(?:[^'\\]|\\[^])*'|\\[^]|\/\*[^]*?\*\//g;function hd(e){let t={};return e.replace(md,n=>n.startsWith("/*")?"":n).split(pd).forEach(n=>{if(n){let r=n.split(fd);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function T(e){let t="";if(ze(e))t=e;else if(ye(e))for(let n=0;n<e.length;n++){let r=T(e[n]);r&&(t+=r+" ")}else if(Te(e))for(let n in e)e[n]&&(t+=n+" ");return t.trim()}var vr="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",gr=ho(vr),vd=ho(vr+",async,autofocus,autoplay,controls,default,defer,disabled,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");function vs(e){return!!e||e===""}function gd(e,t,n){if(e.length!==t.length)return!1;let r=!0;for(let s=0;r&&s<e.length;s++)r=Nt(e[s],t[s],n);return r}function fr(e,t,n){if(e.size!==t.size)return!1;let r=Array.from(t),s=new Uint8Array(r.length);for(let i of e){let a=-1;for(let u=0;u<r.length;u++)if(!s[u]&&Nt(i,r[u],n)){a=u;break}if(a<0)return!1;s[a]=1Line truncated
`))),e[uo]=Ws(s);let i=r||s.props&&s.props.type==="number";lo(e,t?"change":"input",a=>{a.target.composing||e[uo](Hn(e.value,n,i))}),(n||i)&&lo(e,"change",()=>{e.value=Hn(e.value,n,i)}),t||(lo(e,"compositionstart",lc),lo(e,"compositionend",Yi),lo(e,"change",Yi))},mounted(e,{value:t,modifiers:{trim:n,number:r}}){let s=t??"",i=e[js];delete e[js],i!==void 0&&(e.type==="text"||e.type==="textarea")&&e.value!==i?e[uo](Hn(e.value,n,r)):e.value=s},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:s,number:i}},a){if(e[uo]=Ws(a),e.composing)return;let u=(i||e.type==="number")&&!/^0\d/.test(e.value)?wo(e.value):e.value,f=t??"";if(u===f)return;let h=e.getRootNode();(h instanceof Document||h instanceof ShadowRoot)&&h.activeElement===e&&e.type!=="range"&&(r&&t===n||s&&e.value.trim()===f)||(e.value=f)}};var ea={deep:!0,created(e,{value:t,modifiers:{number:n}},r){e._modelValue=t,lo(e,"change",()=>{let s=Array.prototype.filter.call(e.options,f=>f.selected).map(f=>n?wo(Ks(f)):Ks(f)),i=e.multiple,a=i?Ft(e._modelValue)?new Set(s):s:s[0],u=e._pendingValue=[i,i?ye(a)?s.slice():s:a];try{e[uo](a)}finally{st(()=>{e._pendingValue===u&&(e._pendingValue=void 0)})}}),e[uo]=Ws(r)},mounted(e,{value:t}){Ji(e,t)},beforeUpdate(e,{value:t},n){e._modelValue=t,e[uo]=Ws(n)},updated(e,{value:t}){let n=e._pendingValue;e._pendingValue=void 0,(!n||n[0]!==e.multiple||!dc(t,n[1],n[0]))&&Ji(e,t)}};function dc(e,t,n){if(!n)return Nt(e,t);if(ye(e))return Nt(e,t);if(Ft(e)){if(e.size!==t.length)return!1;for(let r of t)if(!e.has(r))return!1;return!0}return!1}function Ji(e,t){let n=e.multiple,r=ye(t);if(!(n&&!r&&!Ft(t))){for(let s=0,i=e.options.length;s<i;s++){let a=e.options[s],u=Ks(a);if(n)if(r){let f=typeof u;f==="string"||f==="number"?a.selected=t.some(h=>String(h)===String(u)):a.selected=yr(t,u)>-1}else a.selected=t.has(u);else if(Nt(Ks(a),t)){e.selectedIndex!==s&&(e.selectedIndex=s);return}}!n&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function Ks(e){return"_value"in e?e._value:e.value}var uc=["ctrl","shift","alt","meta"],cc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>uc.some(n=>e[`${n}Key`]&&!t.includes(n))},me=(e,t)=>{if(!e)return e;let n=e._withMods||(e._withMods={}),r=t.join(".");return n[r]||(n[r]=((s,...i)=>{for(let a=0;a<t.length;a++){let u=cc[t[a]];if(u&&u(s,t))return}return e(s,...i)}))},pc={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},Ro=(e,t)=>{let n=e._withKeys||(e._withKeys={}),r=t.join(".");return n[r]||(n[r]=(s=>{if(!("key"in s))return;let i=St(s.key);if(t.some(a=>a===i||pc[a]===i))return e(s)}))},fc=Ge({patchProp:rc},Bu),Qi;function mc(){return Qi||(Qi=Ai(fc))}var ta=((...e)=>{let t=mc().createApp(...e),{mount:n}=t;return t.mount=r=>{let s=vc(r);if(!s)return;let i=t._component;!we(i)&&!i.render&&!i.template&&(i.template=s.innerHTML),s.nodeType===1&&(s.textContent="");let a=n(s,!1,hc(s));return s instanceof Element&&(s.removeAttribute("v-cloak"),s.setAttribute("data-v-app","")),a},t});function hc(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function vc(e){return ze(e)?document.querySelector(e):e}var fo=typeof document<"u";function sa(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function gc(e){return e.__esModule||e[Symbol.toStringTag]==="Module"||e.default&&sa(e.default)}var Oe=Object.assign;function Ys(e,t){let n={};for(let r in t){let s=t[r];n[r]=kt(s)?s.map(e):e(s)}return n}var Ao=()=>{},kt=Array.isArray;function Wn(e,t){let n={};for(let r in e)n[r]=r in t?t[r]:e[r];return n}var na=/#/g,yc=/&/g,bc=/\//g,wc=/=/g,kc=/\?/g,ra=/\+/g,Ec=/%5B/g,Cc=/%5D/g,ia=/%5E/g,_c=/%60/g,aa=/%7B/g,Sc=/%7C/g,la=/%7D/g,Nc=/%20/g;function Kn(e){return e==null?"":encodeURI(""+e).replace(Sc,"|").replace(Ec,"[").replace(Cc,"]")}function da(e){return Kn(e).replace(aa,"{").replace(la,"}").replace(ia,"^")}function jn(e){return Kn(e).replace(ra,"%2B").replace(Nc,"+").replace(na,"%23").replace(yc,"%26").replace(_c,"`").replace(aa,"{").replace(la,"}").replace(ia,"^")}function Dc(e){return jn(e).replace(wc,"%3D")}function Rc(e){return Kn(e).replace(na,"%23").replace(kc,"%3F")}function ua(e){return Rc(e).replace(bc,"%2F")}function To(e){if(e==null)return null;try{return decodeURIComponent(""+e)}catch{}return""+e}var Tc=/\/$/,Ac=e=>e.replace(Tc,"");function Js(e,t,n="/"){let r,s={},i="",a="",u=t.indexOf("#"),f=t.indexOf("?");return f=u>=0&&f>u?-1:f,f>=0&&(r=t.slice(0,f),i=t.slice(f,u>0?u:t.length),s=e(i.slice(1))),u>=0&&(r=r||t.slice(0,u),a=t.slice(u,t.length)),r=Ic(r??t,n),{fullPath:r+i+a,path:r,query:s,hash:To(a)}}function ca(e,t){let n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function Yn(e,t){return!t||!e.toLowerCase().sLine truncated
${JSON.stringify(e)}${t?`
while being at
`+JSON.stringify(t):""}`},[je.NAVIGATION_GUARD_REDIRECT]({from:e,to:t}){return`Redirected from "${e.fullPath}" to "${qc(t)}" via a navigation guard.`},[je.NAVIGATION_ABORTED]({from:e,to:t}){return`Navigation aborted from "${e.fullPath}" to "${t.fullPath}" via a navigation guard.`},[je.NAVIGATION_CANCELLED]({from:e,to:t}){return`Navigation cancelled from "${e.fullPath}" to "${t.fullPath}" with a new navigation.`},[je.NAVIGATION_DUPLICATED]({from:e,to:t}){return`Avoided redundant navigation to current location: "${e.fullPath}".`}};function po(e,t){return Oe(new Error,{type:e,[ga]:!0},t)}function $t(e,t){return e instanceof Error&&ga in e&&(t==null||!!(e.type&t))}var Vc=["params","query","hash"];function qc(e){if(typeof e=="string")return e;if(e.path!=null)return e.path;let t={};for(let s of Vc)s in e&&(t[s]=e[s]);return JSON.stringify(t,null,2)}function ya(e){let t={};if(e===""||e==="?")return t;let s=(e[0]==="?"?e.slice(1):e).split("&");for(let r=0;r<s.length;++r){let n=s[r].replace(na," "),i=n.indexOf("="),a=To(i<0?n:n.slice(0,i)),u=i<0?null:To(n.slice(i+1));if(a in t){let f=t[a];wt(f)||(f=t[a]=[f]),f.push(u)}else t[a]=u}return t}function Zn(e){let t="";for(let s in e){let r=e[s];if(s=Sc(s),r==null){r!==void 0&&(t+=(t.length?"&":"")+s);continue}(wt(r)?r.map(n=>n&&jn(n)):[r&&jn(r)]).forEach(n=>{n!==void 0&&(t+=(t.length?"&":"")+s,n!=null&&(t+="="+n))})}return t}function ba(e){let t={};for(let s in e){let r=e[s];r!==void 0&&(t[s]=wt(r)?r.map(n=>n==null?null:""+n):r==null?r:""+r)}return t}var wa=Symbol(""),xn=Symbol(""),Zs=Symbol(""),xs=Symbol(""),en=Symbol("");function Oo(){let e=[];function t(r){return e.push(r),()=>{let n=e.indexOf(r);n>-1&&e.splice(n,1)}}function s(){e=[]}return{add:t,list:()=>e.slice(),reset:s}}function Kt(e,t,s,r,n,i=a=>a()){let a=r&&(r.enterCallbacks[n]=r.enterCallbacks[n]||[]);return()=>new Promise((u,f)=>{let h=m=>{m===!1?f(po(je.NAVIGATION_ABORTED,{from:s,to:t})):m instanceof Error?f(m):Pc(m)?f(po(je.NAVIGATION_GUARD_REDIRECT,{from:t,to:m})):(a&&r.enterCallbacks[n]===a&&typeof m=="function"&&a.push(m),u())},v=i(()=>e.call(r&&r.instances[n],t,s,h)),k=Promise.resolve(v);e.length<3&&(k=k.then(h)),k.catch(m=>f(m))})}function tn(e,t,s,r,n=i=>i()){let i=[];for(let a of e)for(let u in a.components){let f=a.components[u];if(!(t!=="beforeRouteEnter"&&!a.instances[u]))if(oa(f)){let h=(f.__vccOpts||f)[t];h&&i.push(Kt(h,s,r,a,u,n))}else{let h=f();i.push(()=>h.then(v=>{if(!v)throw new Error(`Couldn't resolve component "${u}" at "${a.path}"`);let k=hc(v)?v.default:v;a.mods[u]=v,a.components[u]=k;let m=(k.__vccOpts||k)[t];return m&&Kt(m,s,r,a,u,n)()}))}}return i}function ka(e,t){let s=[],r=[],n=[],i=Math.max(t.matched.length,e.matched.length);for(let a=0;a<i;a++){let u=t.matched[a];u&&(e.matched.find(h=>co(h,u))?r.push(u):s.push(u));let f=e.matched[a];f&&(t.matched.find(h=>co(h,f))||n.push(f))}return[s,r,n]}var Mc=()=>location.protocol+"//"+location.host;function Ia(e,t){let{pathname:s,search:r,hash:n}=t,i=e.indexOf("#");if(i>-1){let a=n.includes(e.slice(i))?e.slice(i).length:1,u=n.slice(a);return u[0]!=="/"&&(u="/"+u),Yn(u,"")}return Yn(s,e)+r+n}function $c(e,t,s,r){let n=[],i=[],a=null,u=({state:m})=>{let y=Ia(e,location),E=s.value,w=t.value,R=0;if(m){if(s.value=y,t.value=m,a&&a===E){a=null;return}R=w?m.position-w.position:0}else r(y);n.forEach(D=>{D(s.value,E,{delta:R,type:Qs.pop,direction:R?R>0?Xs.forward:Xs.back:Xs.unknown})})};function f(){a=s.value}function h(m){n.push(m);let y=()=>{let E=n.indexOf(m);E>-1&&n.splice(E,1)};return i.push(y),y}function v(){if(document.visibilityState==="hidden"){let{history:m}=window;if(!m.state)return;m.replaceState(Oe({},m.state,{scroll:us()}),"")}}function k(){for(let m of i)m();i=[],window.removeEventListener("popstate",u),window.removeEventListener("pagehide",v),document.removeEventListener("visibilitychange",v)}return window.addEventListener("popstate",u),window.addEventListener("pagehide",v),document.addEventListener("visibilitychange",v),{pauseListeners:f,listen:h,destroy:k}}function Ea(e,t,s,r=!1,n=!1){return{back:e,current:t,forward:s,replaced:r,position:window.history.length,scroll:n?us():null}}function Lc(e){let{history:t,location:s}=window,r={value:Ia(e,s)},n={value:t.state};n.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function i(f,h,v){let k=e.indexOf("#"),m=k>-1?(s.host&&document.querySelector("base")?e:e.slice(k))+f:Mc()+e+f;try{t[v?"replaceState":"pushState"](h,"",m),n.value=h}catch(y){console.error(y),s[v?"replace":"assign"](m)}}function a(f,h){i(f,Oe({},t.state,Ea(n.value.back,f,n.value.forward,!0),h,{position:n.value.position}),!0),r.value=f}function u(f,h){let v=Oe({},n.value,t.state,{forward:f,scroll:us()});i(v.current,v,!0),i(f,Oe({},Ea(r.value,f,null),{position:v.position+1},h),!1),r.value=f}return{location:r,state:n,push:u,replace:a}}function Pa(e){e=pa(e);let t=Lc(e),s=$c(e,t.state,t.location,t.replace);function r(i,a=!0){a||s.pauseListeners(),history.go(i)}let n=Oe({locatLine truncated
`)),e.options=Object.assign({},e.options,h.options),i.restore()})}a();let u=null;e.saveDefault=f=>{if(f&&f.preventDefault&&f.preventDefault(),e.saving)return;let h=i.checkpoint(),v={terms:e.terms.split(`
`).map(k=>k.trim()).filter(k=>k.length>0),options:e.options};e.saving=!0,e.error=null,t.post("/api/user/default",v).then(()=>{i.accept(h),e.saving=!1,e.message=e.unsavedChanges?null:"Saved",u&&r.cancel(u),u=r(()=>{e.message=null},2500)},k=>{e.saving=!1;let y="ERRORS."+(k&&k.data&&k.data.error);s(y).then(E=>{e.error=E&&E!==y?E:"Unable to save your defaults. Please try again."},()=>{e.error="Unable to save your defaults. Please try again."})})},e.deleteAccount=()=>{confirm("Delete your account? All your anonymized repositories, gists, and pull requests will be removed, and your personal data will be erased. This cannot be undone.")&&(e.deletingAccount=!0,t.delete("/api/user").then(()=>{window.location.href="/"},()=>{e.deletingAccount=!1,e.deleteError="Unable to delete the account. Please try again."}))}},Ha=function(e,t,s){e.repoId=null,e.repoUrl=null,e.claim=()=>{t.post("/api/repo/claim",{repoId:e.repoId,repoUrl:e.repoUrl}).then(r=>{s.url("/dashboard")},r=>{e.error=r.data,e.claimForm.repoUrl.setValidity("not_found",!1),e.claimForm.repoId.setValidity("not_found",!1)})}},Ba=function(e,t,s,r,n){e.user&&!e.user.status&&s.url("/dashboard"),e.watch("user.status",()=>{e.user&&!e.user.status&&s.url("/dashboard")}),e.features=[{key:"anonymize",num:"01",eyebrow:"Anonymize",title:"Double-anonymous,",accent:"your rules.",text:"Choose what reviewers may see: links, images, PDFs, notebooks, GitHub Pages. Add your own terms, with regex if you need it, and pick the expiration date.",cta:"Start an anonymization",href:"/anonymize",url:"anonymous.4open.science/anonymize",img:"/imgs/anonymize.png",alt:"The anonymize form: source repository, terms to redact, options and a live README preview"},{key:"review",num:"02",eyebrow:"Review",title:"Reviewers browse",accent:"the real thing.",text:"Highlighted source code, rendered PDFs, images, and notebooks, in a familiar file explorer. GitHub Pages is also supported.",cta:"Open the example",href:"https://anonymous.4open.science/r/840c8c57-3c32-451e-bf12-0e20be300389/",target:"_self",url:"anonymous.4open.science/r/840c8c57-\u2026",img:"/imgs/explorer.png",alt:"The repository explorer with a file tree and a rendered README"},{key:"manage",num:"03",eyebrow:"Manage",title:"One dashboard,",accent:"until the decision.",text:"Monitor views, edit configuration, remove or update your repository. Program chairs can group submissions under a conference with one shared expiry.",cta:"Open the dashboard",href:"/dashboard",needsUser:!0,url:"anonymous.4open.science/dashboard",img:"/imgs/dashboard.png",alt:"The dashboard listing anonymized repositories with status, views and expiry"}],e.feature=e.features[0].key,e.selectFeature=function(u){e.feature=u},e.featureHref=function(u){return u.needsUser&&!e.user?"/signin":u.href},e.featureTarget=function(u){return u.needsUser&&!e.user?"_self":u.target||void 0},e.featureKeydown=function(u,f){let h={ArrowDown:1,ArrowRight:1,ArrowUp:-1,ArrowLeft:-1,Home:"first",End:"last"}[u.key];if(h===void 0)return;u.preventDefault();let v=e.features.length,k=h==="first"?0:h==="last"?v-1:(f+h+v)%v;e.feature=e.features[k].key,n(()=>{let m=r.document.getElementById("feature-tab-"+e.feature);m&&m.focus()})},e.cards=[{key:"repositories",total:0,label:"repositories anonymized"},{key:"users",total:0,label:"researchers"},{key:"pageViews",total:0,label:"page views"},{key:"pullRequests",total:0,label:"pull requests"}];function i(){t.get("/api/stat/").then(u=>{e.stat=u.data,e.cards[0].total=u.data.nbRepositories,e.cards[1].total=u.data.nbUsers,e.cards[2].total=u.data.nbPageViews,e.cards[3].total=u.data.nbPullRequests})}i();function a(u){let f={series:u,bars:[],viewW:100,deltaToday:0,pctChange:0,pctAbs:0,isUp:!0};if(!u||u.length<2)return f;let h=new Array(u.length-1);for(let w=1;w<u.length;w++)h[w-1]=u[w]-u[w-1];let v=h.length,k=Math.max.apply(null,h),m=Math.min.apply(null,h),y=Math.min(0,m),E=k-y||1;f.viewW=v*2,f.bars=new Array(v);for(let w=0;w<v;w++){let R=(h[w]-y)/E,D=Math.max(1.5,R*34);f.bars[w]={x:(w*2+.25).toFixed(2),y:(36-D).toFixed(2),w:"1.5",h:D.toFixed(2)}}if(f.deltaToday=h[v-1],v>=2){let w=h[v-2];w&&(f.pctChange=(f.deltaToday-w)/w*100)}return f.pctAbs=Math.round(Math.abs(f.pctChange)),f.isUp=f.pctChange>=0,f}e.history={repositories:a([]),users:a([]),pageViews:a([]),pullRequests:a([])},t.get("/api/stat/history?days=60").then(u=>{let f=u.data||[];e.history={repositories:a(f.map(h=>h.nbRepositories||0)),users:a(f.map(h=>h.nbUsers||0)),pageViews:a(f.map(h=>h.nbPageViews||0)),pullRequests:a(f.map(h=>h.nbPullRequests||0))}})},ja=function(e,t,s,r,n,i){let a=ct();e.on("routeLeave",function(){$('[data-toggle="tooltip"]').tooltip("dispose")}),e.watch("user.status",()=>{e.user==null&&s.url("/")}),e.user==null&&s.url("/"),a.timeout(()=>{$('[data-toggle="tooltip"]').tooltip()},250),e.quotaExpanded=!1,e.closeQuota=()=>{e.quotaExpanded=!1,st(()=>document.querySelector(".dashboard-quota > summary")?.focus())},Jt()(document,"click",C=>{e.quotaExpanded&&!C.target.closest?.(".dashboard-quota")&& Line truncated
`)),e.options=Object.assign({},e.options,B.options),e.options.expirationDate=B.options&&B.options.expirationDate?new Date(B.options.expirationDate):A(),!r.repoId&&!r.pullRequestId&&!r.gistId&&!e.anonymize?.terms?.dirty&&(e.terms=e.defaultTerms),_&&_()})}function U(_,I,B){e.anonymize&&e.anonymize[_]&&e.anonymize[_].setValidity(I,B)}function ae(_){try{let I=parseGithubUrl(_);if(I&&I.owner&&I.repo)return I.owner+"/"+I.repo}catch{}return null}e.reconnectingSource=!1,e.reconnectRepositoryId=void 0,e.reconnectSource=async()=>{if(confirm("Reconnect to the repository currently at this GitHub URL? Review its branch and commit before saving. Saving will replace the published snapshot.")){e.reconnectingSource=!0,e.reconnectRepositoryId=void 0,e.source.commit="",e.source.branch="",e.readme="",e.branches=[];try{await G()}catch{}}};function ie(){return e.reconnectingSource||!e.isUpdate||!e._originalRepositoryID?void 0:ae(e.sourceUrl)===e._originalFullName?e._originalRepositoryID:void 0}async function C(){e._preservingDraft=!0;try{await e.urlSelected(!0)}finally{e._preservingDraft=!1}}async function O(){let _;try{_=JSON.parse(sessionStorage.getItem("github-access-draft")||"null")}catch{return!1}if(!_||_.path!==n.path()||Date.now()-_.savedAt>=30*6e4)return R.restore()?(e.sourceUrl&&await C(),!0):!1;R.restore();for(let I of["sourceUrl","terms","repoId","pullRequestId","gistId","source","options","conference","githubConnection"])Object.prototype.hasOwnProperty.call(_.draft,I)&&(e[I]=_.draft[I]);return e.options.expirationDate&&(e.options.expirationDate=new Date(e.options.expirationDate)),await C(),sessionStorage.removeItem("github-access-draft"),!0}q(()=>{!r.repoId&&!r.pullRequestId&&!r.gistId&&a(O,0),r.repoId&&r.repoId!=""&&(e.isUpdate=!0,e.detectedType="repo",e.repoId=r.repoId,t.get("/api/repo/"+e.repoId).then(async _=>{e.githubConnection=_.data.connection||"oauth",e.sourceUrl="https://github.com/"+_.data.source.fullName,e._originalFullName=_.data.source.fullName,e.terms=_.data.options.terms.filter(I=>I).join(`
`),e.source=_.data.source,e.role=_.data.role||"owner",e.coauthors=_.data.coauthors||[],e._originalBranch=_.data.source.branch,e.options=Object.assign({},e.options,_.data.options),e.conference=_.data.conference,e._originalConference=_.data.conference,e.repositoryID=_.data.source.repositoryID,e._originalRepositoryID=_.data.source.repositoryID,_.data.options.expirationDate&&(e.options.expirationDate=new Date(_.data.options.expirationDate)),E=w(parseGithubUrl(e.sourceUrl)),!await O()&&(await Promise.all([G(),J()]),ue())},()=>{n.url("/404")})),r.pullRequestId&&r.pullRequestId!=""&&(e.isUpdate=!0,e.detectedType="pr",e.pullRequestId=r.pullRequestId,t.get("/api/pr/"+e.pullRequestId).then(async _=>{if(e.githubConnection=_.data.connection||"oauth",e.sourceUrl="https://github.com/"+_.data.source.repositoryFullName+"/pull/"+_.data.source.pullRequestId,e.terms=_.data.options.terms.filter(I=>I).join(`
`),e.source=_.data.source,e.options=Object.assign({},e.options,_.data.options),e.conference=_.data.conference,e._originalConference=_.data.conference,_.data.options.expirationDate&&(e.options.expirationDate=new Date(_.data.options.expirationDate)),E=w(parseGithubUrl(e.sourceUrl)),!await O())try{await pe()}catch{}},()=>{n.url("/404")})),r.gistId&&r.gistId!=""&&(e.isUpdate=!0,e.detectedType="gist",e.gistId=r.gistId,t.get("/api/gist/"+e.gistId).then(async _=>{e.sourceUrl="https://gist.github.com/"+_.data.source.gistId,e.terms=_.data.options.terms.filter(I=>I).join(`
`),e.source=_.data.source,e.options=Object.assign({},e.options,_.data.options),e.conference=_.data.conference,e._originalConference=_.data.conference,_.data.options.expirationDate&&(e.options.expirationDate=new Date(_.data.options.expirationDate)),E=w(parseGithubUrl(e.sourceUrl)),!await O()&&await Ae()},()=>{n.url("/404")}))}),t.get("/github/connections").then(_=>{e.githubConnections=_.data,!r.repoId&&!r.pullRequestId&&!r.gistId&&e.githubConnection===void 0&&_.data.appEnabled&&_.data.appConnected&&(e.githubConnection="github-app",e.sourceUrl&&e.workspaceOpen&&C())}).catch(()=>{}),e.urlSelected=async(_=!1)=>{let I=++f,B;try{B=parseGithubUrl(e.sourceUrl.trim())}catch{e.sourceLoading=!1,oe.cancel(),_e.cancel(),Se.cancel(),e.previewStatus="idle",e.sourceUrlError=e.sourceUrl?"Please provide a valid GitHub URL.":"",U("sourceUrl","github",!1);return}let M=w(B),g=_||E===M;E=M,e.sourceUrlError="",U("sourceUrl","github",!0);let S=document.activeElement?.id==="sourceUrl-landing";e.workspaceOpen=!0,S&&a(()=>document.getElementById("sourceUrl")?.focus(),0),e.reconnectingSource=!1,e.reconnectRepositoryId=void 0,!g&&!e.isUpdate&&(e.repoId="",e.pullRequestId="",e.gistId=""),e.details=null,e.branches=[],g||(e.source={type:"GitHubStream",branch:"",commit:""}),oe.cancel(),_e.cancel(),Se.cancel(),le.clear(),Ee.clear(),e.previewStatus="idle",e.anonymize_readme="",e.readme="",e.html_readme="",e.detectedType=null,e.gistOAuthRequired=!1,e.gistAppRequired=!1,e._preservingDraft=g;try{B.gistId&&!B.repo?(e.detectedType="gist",e.source={gistId:B.gistId},await Ae()):B.pullRequestId?(e.detectedType="pr",e.source={repositoryFullName:B.owner+"/"+B.repo,pullRequestId:B.pullRequestId},await pe()):(e.detectedType="repo",await Promise.all([G(),J()]),ue())}catch{}finally{I===f&&(e._preservingDraft=!1)}$('[data-toggle="tooltip"]').tooltip()},$('[data-toggle="tooltip"]').tooltip(),e.watch("source.branch",async()=>{if(e.detectedType!=="repo")return;let _=e.branches.filter(B=>B.name==e.source.branch)[0];if(!_)return;!(!e.reconnectingSource&&e.isUpdate&&e._originalBranch===e.source.branch&&!!e.source.commit)&&!(e._preservingDraft&&e.source.commit)&&(e.source.commit=_.commit),e.readme=_.readme,await J(),ue()}),e.getBranches=async _=>{let I=parseGithubUrl(e.sourceUrl),B=m();try{let M=await t.get(`/api/repo/${I.owner}/${I.repo}/branches`,{params:{reconnect:e.reconnectingSource?"1":void 0,anonymizedRepoId:e.isUpdate&&r.repoId&&ae(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,force:_===!0||e.reconnectingSource?"1":"0",repositoryID:ie()}});if(!k(B))return;e.branches=M.data,e.sourceUnreachable=!1,e.source.branch||(e.source.branch=e.details.defaultBranch);let g=e.branches.filter(S=>S.name==e.source.branch);g.length>0&&(!(!e.reconnectingSource&&!_&&e.isUpdate&&!e.options.update&&e._originalBranch===e.source.branch&&e.source.commit)&&!(e._preservingDraft&&e.source.commit)&&(e.source.commit=g[0].commit),e.readme=g[0].readme,await J(_))}catch(M){if(!k(B))return;e.branches=[],e.sourceUnreachable=M&&(M.status===404||M.data&&M.data.error==="repo_not_found");let g=M&&M.data&&M.data.error||(M&&M.status===404?"repo_not_found":"unknown_error");i("ERRORS."+g).then(S=>{e.toasts=e.toasts||[],e.addToast({title:"Error",date:new Date,body:S}),e.error=S},console.error),typeof U=="function"&&U("sourceUrl","missing",!1)}finally{y(B)}};async function G(){let _=parseGithubUrl(e.sourceUrl),I=m();try{z();let B=await t.get(`/api/repo/${_.owner}/${_.repo}/`,{params:{reconnect:e.reconnectingSource?"1":void 0,anonymizedRepoId:e.isUpdate&&r.repoId&&ae(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,repositoryID:ie(),force:"1"}});if(!k(I))return;e.details=B.data,e.reconnectingSource&&(e.reconnectRepositoryId=e.details.externalId),e.details&&e.details.id&&(e.repositoryID=e.details.id),e.repoId||(e.repoId=e.details.repo+"-"+generateRandomId(4)),await e.getBranches(e.reconnectingSource)}catch(B){if(!k(I))return;throw B.data&&(i("ERRORS."+B.data.error).then(M=>{e.addToast({title:"Error",date:new Date,body:M}),e.error=M},console.error),te(B.data.error)),U("sourceUrl","missing",!1),B}finally{y(I)}}async function J(_){if(e.readme&&!_)return e.readme;let I=parseGithubUrl(e.sourceUrl),B=m();try{let M=await t.get(`/api/repo/${I.owner}/${I.repo}/readme`,{params:{reconnect:e.reconnectingSource?"1":void 0,anonymizedRepoId:e.isUpdate&&r.repoId&&ae(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,force:_===!0?"1":"0",branch:e.source.branch,repositoryID:ie()}});k(B,!0)&&(e.readme=M.data)}catch{k(B,!0)&&(e.readme="")}finally{y(B)}}function ee(){let _={terms:e.terms?e.terms.split(`
`):[],image:!!e.options.image,link:!!e.options.link,repoId:e.repoId};e.source&&e.source.branch&&(_.branchName=e.source.branch);try{let I=parseGithubUrl(e.sourceUrl);_.repoName=`${I.owner}/${I.repo}`}catch{}return _}function Q(_,I,B){let M=null,g=0,S=()=>{if(e.detectedType!==_)return;M&&a.cancel(M);let V=++g;e.previewStatus="loading",M=a(()=>{M=null;let F=I();if(!F){e.previewStatus="idle";return}t.post("/api/anonymize-preview",F).then(j=>{V===g&&(B(j.data,F),e.previewStatus="ready")}).catch(()=>{V===g&&(e.previewStatus="error")})},200)};return S.cancel=()=>{g++,M&&a.cancel(M),M=null},e.on("dispose",S.cancel),S}e.retryPreview=()=>{e.detectedType==="repo"&&oe(),e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()};let oe=Q("repo",()=>e.readme?{content:e.readme,options:ee()}:null,_=>{if(typeof _?.content!="string")throw Error("Invalid preview response");e.anonymize_readme=_.content;let I="";try{let M=parseGithubUrl(e.sourceUrl),g=e.source.branch||e.details&&e.details.defaultBranch||"main";I=`https://github.com/${M.owner}/${M.repo}/raw/${g}/`}catch{}let B=renderMD(e.anonymize_readme,I);e.html_readme=B,a(Prism.highlightAll,150)});function ue(){!e.anonymize||!e.anonymize.terms||(e.termsRegexWarning=!!e.terms&&!!e.terms.match(/[-[\]{}()*+?.,\\^$|#]/g),oe())}async function pe(){let _=parseGithubUrl(e.sourceUrl),I=m();try{z();let B=await t.get(`/api/pr/${_.owner}/${_.repo}/${_.pullRequestId}`,{params:{connection:e.githubConnection}});if(!k(I))return;e.details=B.data,e.pullRequestId||(e.pullRequestId=_.repo+"-PR"+_.pullRequestId+"-"+generateRandomId(4))}catch(B){if(!k(I))return;throw B.data&&(i("ERRORS."+B.data.error).then(M=>{e.addToast({title:"Error",date:new Date,body:M}),e.error=M},console.error),te(B.data.error)),U("sourceUrl","missing",!1),B}finally{y(I)}}let le=He(new Map);function Ie(){let _=new Set,I=e.details&&e.details.pullRequest;if(!I)return _;typeof I.title=="string"&&_.add(I.title),typeof I.body=="string"&&_.add(I.body),typeof I.diff=="string"&&_.add(I.diff);let B=I.comments||[];for(let M of B)typeof M.author=="string"&&_.add(M.author),typeof M.body=="string"&&_.add(M.body);return _}let _e=Q("pr",()=>{let _=Ie(),I=Array.from(_);return I.length===0?null:{contents:I,options:ee()}},(_,I)=>{if(!Array.isArray(_?.contents)||_.contents.length!==I.contents.length||_.contents.some(g=>typeof g!="string"))throw Error("Invalid preview response");let B=I.contents,M=new Map;for(let g=0;g<B.length&&g<_.contents.length;g++)M.set(B[g],_.contents[g]);le.clear(),M.forEach((g,S)=>le.set(S,g))});e.anonymizePrContent=function(_){return _&&(le.has(_)?le.get(_):"")};async function Ae(){let _=parseGithubUrl(e.sourceUrl),I=m();try{z(),e.gistOAuthRequired=!1,e.gistAppRequired=!1;let B=await t.get(`/api/gist/source/${_.gistId}`);if(!k(I))return;e.details=B.data,e.gistId||(e.gistId="gist-"+_.gistId.substring(0,6)+"-"+generateRandomId(4))}catch(B){if(!k(I))return;if(B.data?.error==="github_app_reconnect_required"){e.gistAppRequired=!0,U("sourceUrl","missing",!1);return}if(B.data?.error==="github_oauth_required"){e.gistOAuthRequired=!0,U("sourceUrl","missing",!1);return}throw B.data&&(i("ERRORS."+B.data.error).then(M=>{e.addToast({title:"Error",date:new Date,body:M}),e.error=M},console.error),te(B.data.error)),U("sourceUrl","missing",!1),B}finally{y(I)}}let Ee=He(new Map);function Ve(){let _=new Set,I=e.details&&e.details.gist;if(!I)return _;typeof I.description=="string"&&_.add(I.description),typeof I.ownerLogin=="string"&&_.add(I.ownerLogin);let B=I.files||[];for(let g of B)typeof g.filename=="string"&&_.add(g.filename),typeof g.content=="string"&&_.add(g.content);let M=I.comments||[];for(let g of M)typeof g.author=="string"&&_.add(g.author),typeof g.body=="string"&&_.add(g.body);return _}let Se=Q("gist",()=>{let _=Ve(),I=Array.from(_);return I.length===0?null:{contents:I,options:ee()}},(_,I)=>{if(!Array.isArray(_?.contents)||_.contents.length!==I.contents.length||_.contents.some(g=>typeof g!="string"))throw Error("Invalid preview response");let B=I.contents,M=new Map;for(let g=0;g<B.length&&g<_.contents.length;g++)M.set(B[g],_.contents[g]);Ee.clear(),M.forEach((g,S)=>Ee.set(S,g)),Le()});e.anonymizeGistContent=function(_){return _&&(Ee.has(_)?Ee.get(_):"")},e.previewGistFiles=[];function Le(){let _=e.details&&e.details.gist&&e.details.gist.files||[];e.previewGistFiles=_.map(I=>({filename:e.anonymizeGistContent(I.filename),content:e.anonymizeGistContent(I.content),language:I.language}))}e.watch("details.gist.files",Le,!0),e.watch("terms",Le);function Ue(){let _=e.conference;if(e.conference_data=null,!_)return;let I=e.isUpdate&&_===e._originalConference;t.get("/api/conferences/"+_).then(B=>{e.conference===_&&(e.conference_data=B.data,e.conference_data.startDate=new Date(e.conference_data.startDate),e.conference_data.endDate=new Date(e.conference_data.endDate),!I&&(e.options.expirationDate=new Date(e.conference_data.endDate),e.options.expirationMode="remove",e.options.update=e.conference_data.options.update,e.options.image=e.con Line truncated
`).filter(S=>S),connection:e.githubConnection,fullName:`${B.owner}/${B.repo}`,repository:e.sourceUrl,reconnectRepositoryId:e.reconnectRepositoryId,options:e.options,source:e.source,conference:e.conference};e.details&&(M.options.pageSource=e.details.pageSource),z();let g=e.isUpdate?"/api/repo/"+e.repoId:"/api/repo/";t.post(g,M,{headers:{"Content-Type":"application/json"}}).then(()=>{R.clear(),window.location.href="/status/"+e.repoId},S=>{S.data&&(i("ERRORS."+S.data.error).then(V=>{e.error=V},console.error),te(S.data.error))}).finally(()=>{I.disabled=!1})},e.anonymizeGist=_=>{if(e.sourceLoading){e.error="Wait for the source to finish loading before saving.";return}if(ne())return;if(e.anonymize?.invalid||e.sourceUrlError){e.error="Please correct the highlighted fields before saving.";return}let I=_.currentTarget||_.target.closest?.("button")||_.target;I.disabled=!0;let B=parseGithubUrl(e.sourceUrl),M={gistId:e.gistId,terms:e.terms.trim().split(`
`).filter(S=>S),source:{gistId:B.gistId},options:e.options,conference:e.conference};z();let g=e.isUpdate?"/api/gist/"+e.gistId:"/api/gist/";t.post(g,M,{headers:{"Content-Type":"application/json"}}).then(()=>{R.clear(),window.location.href="/gist/"+e.gistId},S=>{S.data&&(i("ERRORS."+S.data.error).then(V=>{e.error=V},console.error),te(S.data.error))}).finally(()=>{I.disabled=!1})},e.anonymizePullRequest=_=>{if(e.sourceLoading){e.error="Wait for the source to finish loading before saving.";return}if(ne())return;if(e.anonymize?.invalid||e.sourceUrlError){e.error="Please correct the highlighted fields before saving.";return}let I=_.currentTarget||_.target.closest?.("button")||_.target;I.disabled=!0;let B=parseGithubUrl(e.sourceUrl),M={pullRequestId:e.pullRequestId,connection:e.githubConnection,terms:e.terms.trim().split(`
`).filter(S=>S),source:{repositoryFullName:`${B.owner}/${B.repo}`,pullRequestId:B.pullRequestId},options:e.options,conference:e.conference};z();let g=e.isUpdate?"/api/pr/"+e.pullRequestId:"/api/pr/";t.post(g,M,{headers:{"Content-Type":"application/json"}}).then(()=>{R.clear(),window.location.href="/pr/"+e.pullRequestId},S=>{S.data&&(i("ERRORS."+S.data.error).then(V=>{e.error=V},console.error),te(S.data.error))}).finally(()=>{I.disabled=!1})},e.watch("conference",()=>{Ue()}),e.watch("terms",()=>{e.detectedType==="repo"&&ue(),e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()}),e.watch("options.image",()=>{e.detectedType==="repo"&&ue(),e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()}),e.watch("options.link",()=>{e.detectedType==="repo"&&ue(),e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()}),e.watch("details",()=>{e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()},!0)},sr=function(e,t,s,r,n,i){let a=ct(),u=Jt();e.on("dark-mode",(O,G)=>{e.aceOption&&(G?e.aceOption.theme="nord_dark":e.aceOption.theme="chrome")});let f=0,h=!1;e.on("dispose",()=>{h=!0,f++,v&&v.resolve()}),e.files=[],e.isMac=/Mac|iPhone|iPad|iPod/.test(navigator.platform||navigator.userAgent),e.fileSearchQuery="",e.fileSearchResults=null,e.fileSearchLoading=!1,u(document,"keydown",function(O){if((O.metaKey||O.ctrlKey)&&O.key==="k"){O.preventDefault();var G=document.querySelector(".tree-search-input");G&&(G.focus(),G.select())}});var v=null;e.onFileSearchChange=function(){v&&(v.resolve(),v=null);let O=e.fileSearchQuery;if(!O||O.length<2){e.fileSearchResults=null,e.fileSearchLoading=!1;return}e.fileSearchLoading=!0;let G=i.defer();v=G,t.get(`/api/repo/${e.repoId}/files/search?q=${encodeURIComponent(O)}`,{timeout:G.promise}).then(function(J){if(!(h||v!==G)){v=null,e.fileSearchLoading=!1;var ee={};e.files.forEach(function(z){ee[(z.path||"")+"/"+z.name]=!0});for(var Q=[],oe={},ue=0;ue<J.data.length;ue++){var pe=J.data[ue];if(pe.path)for(var le=pe.path.split("/"),Ie="",_e=0;_e<le.length;_e++){var Ae=Ie;Ie=Ie?Ie+"/"+le[_e]:le[_e];var Ee=Ae+"/"+le[_e];!ee[Ee]&&!oe[Ee]&&(oe[Ee]=!0,Q.push({name:le[_e],path:Ae}))}}Q.sort(function(z,ne){return(z.path||"").split("/").length-(ne.path||"").split("/").length}),Q.length>0&&e.files.push.apply(e.files,Q);for(var Ve=[],Se=0;Se<J.data.length;Se++){var Le=J.data[Se],Ue=(Le.path||"")+"/"+Le.name;!ee[Ue]&&Le.size!=null&&(Ve.push(Le),ee[Ue]=!0)}Ve.length>0&&e.files.push.apply(e.files,Ve),e.fileSearchResults=J.data}},function(){!h&&v===G&&(v=null,e.fileSearchLoading=!1,e.fileSearchResults=[])})};let k={yml:"yaml",txt:"text",py:"python",js:"javascript",ts:"typescript"},m=["license","txt"],y=["png","jpg","jpeg","gif","svg","ico","bmp","tiff","tif","webp","avif","heif","heic"],E=["wav","mp3","ogg","wma","flac","aac","m4a"],w=["mp4","avi","webm","mov","mpg","mpeg","mkv","flv","wmv","3gp","3g2","m4v","f4v","f4p","f4a","f4b"];e.on("routeUpdate",function(O,G){if(e.repoId!=r.repoId)return C();if((r.path||"")!=e.filePath){e.filePath=r.path||"",e.paths=e.filePath.split("/").filter(J=>J&&J.trim().length>0),ie();for(let J=0;J<e.paths.length-1;J++){let ee=J>0?e.paths.slice(0,J).join("/"):"";e.files.some(oe=>oe.path===ee)||e.getFiles(ee)}}});function R(){if(e.paths[0]!="")return;let O=["readme.md","readme.txt","readme.org","readme.1st","readme"],G={};for(let ee of e.files)ee.name.toLowerCase().indexOf("readme")>-1&&(G[ee.name.toLowerCase()]=ee.name);let J=null;for(let ee of O)if(G[ee]){J=ee;break}if(!J&&Object.keys(G).length>0&&(J=Object.keys(G)[0]),J){let ee=s.url();ee[ee.length-1]!="/"&&(ee+="/"),s.url(ee+encodePathForUrl(G[J]))}}e.fileCounts=null,e.getFiles=function(O){let G=e.repoId;return t.get(`/api/repo/${e.repoId}/files/?path=${encodeURIComponent(O)}&v=${e.options.lastUpdateDate}`).then(function(J){if(h||G!==e.repoId)return[];let ee=O||"";return e.files=e.files.filter(Q=>Q.path!==ee),e.files.push(...J.data),J.data},function(J){if(h||G!==e.repoId)return[];e.type="error",e.content=J&&J.data&&J.data.error||"unknown_error",e.files=[]})};function D(){let O=e.repoId;t.get(`/api/repo/${e.repoId}/files/counts`).then(function(G){h||O!==e.repoId||(e.fileCounts=G.data)},function(){e.fileCounts={}})}function A(){return e.files.filter(O=>O.name==e.paths[e.paths.length-1]&&O.path==e.paths.slice(0,e.paths.length-1).join("/"))[0]}var P=null;e.on("dispose",function(){P&&a.interval.cancel(P)});function H(O){if(h)return;let G=e.repoId;t.get(`/api/repo/${e.repoId}/options`).then(J=>{if(!(h||G!==e.repoId)){if(e.options=J.data,e.options.url){window.location=e.options.url;return}O&&O(J.data)}},J=>{if(!(h||G!==e.repoId)){var ee=J.data||{};if(ee.error==="rate_limited"&&ee.resetAt){let oe=function(){var ue=Math.max(0,Math.ceil((e.rateLimitResetAt-Date.now())/1e3));if(ue<=0)e.rateLimitCountdown="",e.rateLimitResetAt=0,P&&(a.interval.cancel(P),P=null),H(O);else{var pe=Math.floor(ue/60),le=ue%60;e.rateLimitCountdown=pe>0?pe+"m "+le+"s":le+"s"}};var Q=oe;e.type="rate_limited",e.rateLimitResetAt=ee.resetAt,P&&a.interval.canc Line truncated
`+JSON.stringify(t):""}`},[je.NAVIGATION_GUARD_REDIRECT]({from:e,to:t}){return`Redirected from "${e.fullPath}" to "${Lc(t)}" via a navigation guard.`},[je.NAVIGATION_ABORTED]({from:e,to:t}){return`Navigation aborted from "${e.fullPath}" to "${t.fullPath}" via a navigation guard.`},[je.NAVIGATION_CANCELLED]({from:e,to:t}){return`Navigation cancelled from "${e.fullPath}" to "${t.fullPath}" with a new navigation.`},[je.NAVIGATION_DUPLICATED]({from:e,to:t}){return`Avoided redundant navigation to current location: "${e.fullPath}".`}};function po(e,t){return Oe(new Error,{type:e,[ya]:!0},t)}function $t(e,t){return e instanceof Error&&ya in e&&(t==null||!!(e.type&t))}var Mc=["params","query","hash"];function Lc(e){if(typeof e=="string")return e;if(e.path!=null)return e.path;let t={};for(let n of Mc)n in e&&(t[n]=e[n]);return JSON.stringify(t,null,2)}function ba(e){let t={};if(e===""||e==="?")return t;let n=(e[0]==="?"?e.slice(1):e).split("&");for(let r=0;r<n.length;++r){let s=n[r].replace(ra," "),i=s.indexOf("="),a=To(i<0?s:s.slice(0,i)),u=i<0?null:To(s.slice(i+1));if(a in t){let f=t[a];kt(f)||(f=t[a]=[f]),f.push(u)}else t[a]=u}return t}function Zn(e){let t="";for(let n in e){let r=e[n];if(n=Dc(n),r==null){r!==void 0&&(t+=(t.length?"&":"")+n);continue}(kt(r)?r.map(s=>s&&jn(s)):[r&&jn(r)]).forEach(s=>{s!==void 0&&(t+=(t.length?"&":"")+n,s!=null&&(t+="="+s))})}return t}function wa(e){let t={};for(let n in e){let r=e[n];r!==void 0&&(t[n]=kt(r)?r.map(s=>s==null?null:""+s):r==null?r:""+r)}return t}var ka=Symbol(""),xn=Symbol(""),Zs=Symbol(""),xs=Symbol(""),en=Symbol("");function Oo(){let e=[];function t(r){return e.push(r),()=>{let s=e.indexOf(r);s>-1&&e.splice(s,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function Yt(e,t,n,r,s,i=a=>a()){let a=r&&(r.enterCallbacks[s]=r.enterCallbacks[s]||[]);return()=>new Promise((u,f)=>{let h=m=>{m===!1?f(po(je.NAVIGATION_ABORTED,{from:n,to:t})):m instanceof Error?f(m):qc(m)?f(po(je.NAVIGATION_GUARD_REDIRECT,{from:t,to:m})):(a&&r.enterCallbacks[s]===a&&typeof m=="function"&&a.push(m),u())},g=i(()=>e.call(r&&r.instances[s],t,n,h)),k=Promise.resolve(g);e.length<3&&(k=k.then(h)),k.catch(m=>f(m))})}function tn(e,t,n,r,s=i=>i()){let i=[];for(let a of e)for(let u in a.components){let f=a.components[u];if(!(t!=="beforeRouteEnter"&&!a.instances[u]))if(sa(f)){let h=(f.__vccOpts||f)[t];h&&i.push(Yt(h,n,r,a,u,s))}else{let h=f();i.push(()=>h.then(g=>{if(!g)throw new Error(`Couldn't resolve component "${u}" at "${a.path}"`);let k=gc(g)?g.default:g;a.mods[u]=g,a.components[u]=k;let m=(k.__vccOpts||k)[t];return m&&Yt(m,n,r,a,u,s)()}))}}return i}function Ea(e,t){let n=[],r=[],s=[],i=Math.max(t.matched.length,e.matched.length);for(let a=0;a<i;a++){let u=t.matched[a];u&&(e.matched.find(h=>co(h,u))?r.push(u):n.push(u));let f=e.matched[a];f&&(t.matched.find(h=>co(h,f))||s.push(f))}return[n,r,s]}var $c=()=>location.protocol+"//"+location.host;function Pa(e,t){let{pathname:n,search:r,hash:s}=t,i=e.indexOf("#");if(i>-1){let a=s.includes(e.slice(i))?e.slice(i).length:1,u=s.slice(a);return u[0]!=="/"&&(u="/"+u),Yn(u,"")}return Yn(n,e)+r+s}function Uc(e,t,n,r){let s=[],i=[],a=null,u=({state:m})=>{let y=Pa(e,location),E=n.value,w=t.value,R=0;if(m){if(n.value=y,t.value=m,a&&a===E){a=null;return}R=w?m.position-w.position:0}else r(y);s.forEach(D=>{D(n.value,E,{delta:R,type:Qs.pop,direction:R?R>0?Xs.forward:Xs.back:Xs.unknown})})};function f(){a=n.value}function h(m){s.push(m);let y=()=>{let E=s.indexOf(m);E>-1&&s.splice(E,1)};return i.push(y),y}function g(){if(document.visibilityState==="hidden"){let{history:m}=window;if(!m.state)return;m.replaceState(Oe({},m.state,{scroll:us()}),"")}}function k(){for(let m of i)m();i=[],window.removeEventListener("popstate",u),window.removeEventListener("pagehide",g),document.removeEventListener("visibilitychange",g)}return window.addEventListener("popstate",u),window.addEventListener("pagehide",g),document.addEventListener("visibilitychange",g),{pauseListeners:f,listen:h,destroy:k}}function Ca(e,t,n,r=!1,s=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:s?us():null}}function Fc(e){let{history:t,location:n}=window,r={value:Pa(e,n)},s={value:t.state};s.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function i(f,h,g){let k=e.indexOf("#"),m=k>-1?(n.host&&document.querySelector("base")?e:e.slice(k))+f:$c()+e+f;try{t[g?"replaceState":"pushState"](h,"",m),s.value=h}catch(y){console.error(y),n[g?"replace":"assign"](m)}}function a(f,h){i(f,Oe({},t.state,Ca(s.value.back,f,s.value.forward,!0),h,{position:s.value.position}),!0),r.value=f}function u(f,h){let g=Oe({},s.value,t.state,{forward:f,scroll:us()});i(g.current,g,!0),i(f,Oe({},Ca(r.value,f,null),{position:g.position+1},h),!1),r.value=f}return{location:r,state:s,push:u,replace:a}}function Va(e){e=fa(e);let t=Fc(e),n=Uc(e,t.state,t.location,t.replace);function r(i,a=!0){a||n.pauseListeners(),history.go(i)}let s=Oe({locatLine truncated
`)),e.options=Object.assign({},e.options,h.options),i.restore()})}a();let u=null;e.saveDefault=f=>{if(f&&f.preventDefault&&f.preventDefault(),e.saving)return;let h=i.checkpoint(),g={terms:e.terms.split(`
`).map(k=>k.trim()).filter(k=>k.length>0),options:e.options};e.saving=!0,e.error=null,t.post("/api/user/default",g).then(()=>{i.accept(h),e.saving=!1,e.message=e.unsavedChanges?null:"Saved",u&&r.cancel(u),u=r(()=>{e.message=null},2500)},k=>{e.saving=!1;let y="ERRORS."+(k&&k.data&&k.data.error);n(y).then(E=>{e.error=E&&E!==y?E:"Unable to save your defaults. Please try again."},()=>{e.error="Unable to save your defaults. Please try again."})})},e.deleteAccount=()=>{confirm("Delete your account? All your anonymized repositories, gists, and pull requests will be removed, and your personal data will be erased. This cannot be undone.")&&(e.deletingAccount=!0,t.delete("/api/user").then(()=>{window.location.href="/"},()=>{e.deletingAccount=!1,e.deleteError="Unable to delete the account. Please try again."}))}},Ba=function(e,t,n){e.repoId=null,e.repoUrl=null,e.claim=()=>{t.post("/api/repo/claim",{repoId:e.repoId,repoUrl:e.repoUrl}).then(r=>{n.url("/dashboard")},r=>{e.error=r.data,e.claimForm.repoUrl.setValidity("not_found",!1),e.claimForm.repoId.setValidity("not_found",!1)})}},ja=function(e,t,n,r,s){e.user&&!e.user.status&&n.url("/dashboard"),e.watch("user.status",()=>{e.user&&!e.user.status&&n.url("/dashboard")}),e.features=[{key:"anonymize",num:"01",eyebrow:"Anonymize",title:"Double-anonymous,",accent:"your rules.",text:"Choose what reviewers may see: links, images, PDFs, notebooks, GitHub Pages. Add your own terms, with regex if you need it, and pick the expiration date.",cta:"Start an anonymization",href:"/anonymize",url:"anonymous.4open.science/anonymize",img:"/imgs/anonymize.png",alt:"The anonymize form: source repository, terms to redact, options and a live README preview"},{key:"review",num:"02",eyebrow:"Review",title:"Reviewers browse",accent:"the real thing.",text:"Highlighted source code, rendered PDFs, images, and notebooks, in a familiar file explorer. GitHub Pages is also supported.",cta:"Open the example",href:"https://anonymous.4open.science/r/840c8c57-3c32-451e-bf12-0e20be300389/",target:"_self",url:"anonymous.4open.science/r/840c8c57-\u2026",img:"/imgs/explorer.png",alt:"The repository explorer with a file tree and a rendered README"},{key:"manage",num:"03",eyebrow:"Manage",title:"One dashboard,",accent:"until the decision.",text:"Monitor views, edit configuration, remove or update your repository. Program chairs can group submissions under a conference with one shared expiry.",cta:"Open the dashboard",href:"/dashboard",needsUser:!0,url:"anonymous.4open.science/dashboard",img:"/imgs/dashboard.png",alt:"The dashboard listing anonymized repositories with status, views and expiry"}],e.feature=e.features[0].key,e.selectFeature=function(u){e.feature=u},e.featureHref=function(u){return u.needsUser&&!e.user?"/signin":u.href},e.featureTarget=function(u){return u.needsUser&&!e.user?"_self":u.target||void 0},e.featureKeydown=function(u,f){let h={ArrowDown:1,ArrowRight:1,ArrowUp:-1,ArrowLeft:-1,Home:"first",End:"last"}[u.key];if(h===void 0)return;u.preventDefault();let g=e.features.length,k=h==="first"?0:h==="last"?g-1:(f+h+g)%g;e.feature=e.features[k].key,s(()=>{let m=r.document.getElementById("feature-tab-"+e.feature);m&&m.focus()})},e.cards=[{key:"repositories",total:0,label:"repositories anonymized"},{key:"users",total:0,label:"researchers"},{key:"pageViews",total:0,label:"page views"},{key:"pullRequests",total:0,label:"pull requests"}];function i(){t.get("/api/stat/").then(u=>{e.stat=u.data,e.cards[0].total=u.data.nbRepositories,e.cards[1].total=u.data.nbUsers,e.cards[2].total=u.data.nbPageViews,e.cards[3].total=u.data.nbPullRequests})}i();function a(u){let f={series:u,bars:[],viewW:100,deltaToday:0,pctChange:0,pctAbs:0,isUp:!0};if(!u||u.length<2)return f;let h=new Array(u.length-1);for(let w=1;w<u.length;w++)h[w-1]=u[w]-u[w-1];let g=h.length,k=Math.max.apply(null,h),m=Math.min.apply(null,h),y=Math.min(0,m),E=k-y||1;f.viewW=g*2,f.bars=new Array(g);for(let w=0;w<g;w++){let R=(h[w]-y)/E,D=Math.max(1.5,R*34);f.bars[w]={x:(w*2+.25).toFixed(2),y:(36-D).toFixed(2),w:"1.5",h:D.toFixed(2)}}if(f.deltaToday=h[g-1],g>=2){let w=h[g-2];w&&(f.pctChange=(f.deltaToday-w)/w*100)}return f.pctAbs=Math.round(Math.abs(f.pctChange)),f.isUp=f.pctChange>=0,f}e.history={repositories:a([]),users:a([]),pageViews:a([]),pullRequests:a([])},t.get("/api/stat/history?days=60").then(u=>{let f=u.data||[];e.history={repositories:a(f.map(h=>h.nbRepositories||0)),users:a(f.map(h=>h.nbUsers||0)),pageViews:a(f.map(h=>h.nbPageViews||0)),pullRequests:a(f.map(h=>h.nbPullRequests||0))}})},Ga=function(e,t,n,r,s,i){let a=ct();e.on("routeLeave",function(){$('[data-toggle="tooltip"]').tooltip("dispose")}),e.watch("user.status",()=>{e.user==null&&n.url("/")}),e.user==null&&n.url("/"),a.timeout(()=>{$('[data-toggle="tooltip"]').tooltip()},250),e.quotaExpanded=!1,e.closeQuota=()=>{e.quotaExpanded=!1,st(()=>document.querySelector(".dashboard-quota > summary")?.focus())},Qt()(document,"click",_=>{e.quotaExpanded&&!_.target.closest?.(".dashboard-quota")&& Line truncated
`)),e.options=Object.assign({},e.options,j.options),e.options.expirationDate=j.options&&j.options.expirationDate?new Date(j.options.expirationDate):A(),!r.repoId&&!r.pullRequestId&&!r.gistId&&!e.anonymize?.terms?.dirty&&(e.terms=e.defaultTerms),S&&S()})}function U(S,I,j){e.anonymize&&e.anonymize[S]&&e.anonymize[S].setValidity(I,j)}function ae(S){try{let I=parseGithubUrl(S);if(I&&I.owner&&I.repo)return I.owner+"/"+I.repo}catch{}return null}e.reconnectingSource=!1,e.reconnectRepositoryId=void 0,e.reconnectSource=async()=>{if(confirm("Reconnect to the repository currently at this GitHub URL? Review its branch and commit before saving. Saving will replace the published snapshot.")){e.reconnectingSource=!0,e.reconnectRepositoryId=void 0,e.source.commit="",e.source.branch="",e.readme="",e.branches=[];try{await G()}catch{}}};function ie(){return e.reconnectingSource||!e.isUpdate||!e._originalRepositoryID?void 0:ae(e.sourceUrl)===e._originalFullName?e._originalRepositoryID:void 0}async function _(){e._preservingDraft=!0;try{await e.urlSelected(!0)}finally{e._preservingDraft=!1}}async function O(){let S;try{S=JSON.parse(sessionStorage.getItem("github-access-draft")||"null")}catch{return!1}if(!S||S.path!==s.path()||Date.now()-S.savedAt>=30*6e4)return R.restore()?(e.sourceUrl&&await _(),!0):!1;R.restore();for(let I of["sourceUrl","terms","repoId","pullRequestId","gistId","source","options","conference","githubConnection"])Object.prototype.hasOwnProperty.call(S.draft,I)&&(e[I]=S.draft[I]);return e.options.expirationDate&&(e.options.expirationDate=new Date(e.options.expirationDate)),await _(),sessionStorage.removeItem("github-access-draft"),!0}q(()=>{!r.repoId&&!r.pullRequestId&&!r.gistId&&a(O,0),r.repoId&&r.repoId!=""&&(e.isUpdate=!0,e.detectedType="repo",e.repoId=r.repoId,t.get("/api/repo/"+e.repoId).then(async S=>{e.githubConnection=S.data.connection||"oauth",e.sourceUrl="https://github.com/"+S.data.source.fullName,e._originalFullName=S.data.source.fullName,e.terms=S.data.options.terms.filter(I=>I).join(`
`),e.source=S.data.source,e.role=S.data.role||"owner",e.coauthors=S.data.coauthors||[],e._originalBranch=S.data.source.branch,e.options=Object.assign({},e.options,S.data.options),e.conference=S.data.conference,e._originalConference=S.data.conference,e.repositoryID=S.data.source.repositoryID,e._originalRepositoryID=S.data.source.repositoryID,S.data.options.expirationDate&&(e.options.expirationDate=new Date(S.data.options.expirationDate)),E=w(parseGithubUrl(e.sourceUrl)),!await O()&&(await Promise.all([G(),J()]),ue())},()=>{s.url("/404")})),r.pullRequestId&&r.pullRequestId!=""&&(e.isUpdate=!0,e.detectedType="pr",e.pullRequestId=r.pullRequestId,t.get("/api/pr/"+e.pullRequestId).then(async S=>{if(e.githubConnection=S.data.connection||"oauth",e.sourceUrl="https://github.com/"+S.data.source.repositoryFullName+"/pull/"+S.data.source.pullRequestId,e.terms=S.data.options.terms.filter(I=>I).join(`
`),e.source=S.data.source,e.options=Object.assign({},e.options,S.data.options),e.conference=S.data.conference,e._originalConference=S.data.conference,S.data.options.expirationDate&&(e.options.expirationDate=new Date(S.data.options.expirationDate)),E=w(parseGithubUrl(e.sourceUrl)),!await O())try{await pe()}catch{}},()=>{s.url("/404")})),r.gistId&&r.gistId!=""&&(e.isUpdate=!0,e.detectedType="gist",e.gistId=r.gistId,t.get("/api/gist/"+e.gistId).then(async S=>{e.sourceUrl="https://gist.github.com/"+S.data.source.gistId,e.terms=S.data.options.terms.filter(I=>I).join(`
`),e.source=S.data.source,e.options=Object.assign({},e.options,S.data.options),e.conference=S.data.conference,e._originalConference=S.data.conference,S.data.options.expirationDate&&(e.options.expirationDate=new Date(S.data.options.expirationDate)),E=w(parseGithubUrl(e.sourceUrl)),!await O()&&await Ae()},()=>{s.url("/404")}))}),t.get("/github/connections").then(S=>{e.githubConnections=S.data,!r.repoId&&!r.pullRequestId&&!r.gistId&&e.githubConnection===void 0&&S.data.appEnabled&&S.data.appConnected&&(e.githubConnection="github-app",e.sourceUrl&&e.workspaceOpen&&_())}).catch(()=>{}),e.urlSelected=async(S=!1)=>{let I=++f,j;try{j=parseGithubUrl(e.sourceUrl.trim())}catch{e.sourceLoading=!1,oe.cancel(),_e.cancel(),Se.cancel(),e.previewStatus="idle",e.sourceUrlError=e.sourceUrl?"Please provide a valid GitHub URL.":"",U("sourceUrl","github",!1);return}let M=w(j),v=S||E===M;E=M,e.sourceUrlError="",U("sourceUrl","github",!0);let C=document.activeElement?.id==="sourceUrl-landing";e.workspaceOpen=!0,C&&a(()=>document.getElementById("sourceUrl")?.focus(),0),e.reconnectingSource=!1,e.reconnectRepositoryId=void 0,!v&&!e.isUpdate&&(e.repoId="",e.pullRequestId="",e.gistId=""),e.details=null,e.branches=[],v||(e.source={type:"GitHubStream",branch:"",commit:""}),oe.cancel(),_e.cancel(),Se.cancel(),le.clear(),Ee.clear(),e.previewStatus="idle",e.anonymize_readme="",e.readme="",e.html_readme="",e.detectedType=null,e.gistOAuthRequired=!1,e.gistAppRequired=!1,e._preservingDraft=v;try{j.gistId&&!j.repo?(e.detectedType="gist",e.source={gistId:j.gistId},await Ae()):j.pullRequestId?(e.detectedType="pr",e.source={repositoryFullName:j.owner+"/"+j.repo,pullRequestId:j.pullRequestId},await pe()):(e.detectedType="repo",await Promise.all([G(),J()]),ue())}catch{}finally{I===f&&(e._preservingDraft=!1)}$('[data-toggle="tooltip"]').tooltip()},$('[data-toggle="tooltip"]').tooltip(),e.watch("source.branch",async()=>{if(e.detectedType!=="repo")return;let S=e.branches.filter(j=>j.name==e.source.branch)[0];if(!S)return;!(!e.reconnectingSource&&e.isUpdate&&e._originalBranch===e.source.branch&&e.source.commit)&&!(e._preservingDraft&&e.source.commit)&&(e.source.commit=S.commit),e.readme=S.readme,await J(),ue()}),e.getBranches=async S=>{let I=parseGithubUrl(e.sourceUrl),j=m();try{let M=await t.get(`/api/repo/${I.owner}/${I.repo}/branches`,{params:{reconnect:e.reconnectingSource?"1":void 0,anonymizedRepoId:e.isUpdate&&r.repoId&&ae(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,force:S===!0||e.reconnectingSource?"1":"0",repositoryID:ie()}});if(!k(j))return;e.branches=M.data,e.sourceUnreachable=!1,e.source.branch||(e.source.branch=e.details.defaultBranch);let v=e.branches.filter(C=>C.name==e.source.branch);v.length>0&&(!(!e.reconnectingSource&&!S&&e.isUpdate&&!e.options.update&&e._originalBranch===e.source.branch&&e.source.commit)&&!(e._preservingDraft&&e.source.commit)&&(e.source.commit=v[0].commit),e.readme=v[0].readme,await J(S))}catch(M){if(!k(j))return;e.branches=[],e.sourceUnreachable=M&&(M.status===404||M.data&&M.data.error==="repo_not_found");let v=M&&M.data&&M.data.error||(M&&M.status===404?"repo_not_found":"unknown_error");i("ERRORS."+v).then(C=>{e.toasts=e.toasts||[],e.addToast({title:"Error",date:new Date,body:C}),e.error=C},console.error),typeof U=="function"&&U("sourceUrl","missing",!1)}finally{y(j)}};async function G(){let S=parseGithubUrl(e.sourceUrl),I=m();try{z();let j=await t.get(`/api/repo/${S.owner}/${S.repo}/`,{params:{reconnect:e.reconnectingSource?"1":void 0,anonymizedRepoId:e.isUpdate&&r.repoId&&ae(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,repositoryID:ie(),force:"1"}});if(!k(I))return;e.details=j.data,e.reconnectingSource&&(e.reconnectRepositoryId=e.details.externalId),e.details&&e.details.id&&(e.repositoryID=e.details.id),e.repoId||(e.repoId=e.details.repo+"-"+generateRandomId(4)),await e.getBranches(e.reconnectingSource)}catch(j){if(!k(I))return;throw j.data&&(i("ERRORS."+j.data.error).then(M=>{e.addToast({title:"Error",date:new Date,body:M}),e.error=M},console.error),te(j.data.error)),U("sourceUrl","missing",!1),j}finally{y(I)}}async function J(S){if(e.readme&&!S)return e.readme;let I=parseGithubUrl(e.sourceUrl),j=m();try{let M=await t.get(`/api/repo/${I.owner}/${I.repo}/readme`,{params:{reconnect:e.reconnectingSource?"1":void 0,anonymizedRepoId:e.isUpdate&&r.repoId&&ae(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,force:S===!0?"1":"0",branch:e.source.branch,repositoryID:ie()}});k(j,!0)&&(e.readme=M.data)}catch{k(j,!0)&&(e.readme="")}finally{y(j)}}function ee(){let S={terms:e.terms?e.terms.split(`
`):[],image:!!e.options.image,link:!!e.options.link,repoId:e.repoId};e.source&&e.source.branch&&(S.branchName=e.source.branch);try{let I=parseGithubUrl(e.sourceUrl);S.repoName=`${I.owner}/${I.repo}`}catch{}return S}function Q(S,I,j){let M=null,v=0,C=()=>{if(e.detectedType!==S)return;M&&a.cancel(M);let P=++v;e.previewStatus="loading",M=a(()=>{M=null;let F=I();if(!F){e.previewStatus="idle";return}t.post("/api/anonymize-preview",F).then(B=>{P===v&&(j(B.data,F),e.previewStatus="ready")}).catch(()=>{P===v&&(e.previewStatus="error")})},200)};return C.cancel=()=>{v++,M&&a.cancel(M),M=null},e.on("dispose",C.cancel),C}e.retryPreview=()=>{e.detectedType==="repo"&&oe(),e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()};let oe=Q("repo",()=>e.readme?{content:e.readme,options:ee()}:null,S=>{if(typeof S?.content!="string")throw Error("Invalid preview response");e.anonymize_readme=S.content;let I="";try{let M=parseGithubUrl(e.sourceUrl),v=e.source.branch||e.details&&e.details.defaultBranch||"main";I=`https://github.com/${M.owner}/${M.repo}/raw/${v}/`}catch{}let j=renderMD(e.anonymize_readme,I);e.html_readme=j,a(Prism.highlightAll,150)});function ue(){!e.anonymize||!e.anonymize.terms||(e.termsRegexWarning=!!e.terms&&!!e.terms.match(/[-[\]{}()*+?.,\\^$|#]/g),oe())}async function pe(){let S=parseGithubUrl(e.sourceUrl),I=m();try{z();let j=await t.get(`/api/pr/${S.owner}/${S.repo}/${S.pullRequestId}`,{params:{connection:e.githubConnection}});if(!k(I))return;e.details=j.data,e.pullRequestId||(e.pullRequestId=S.repo+"-PR"+S.pullRequestId+"-"+generateRandomId(4))}catch(j){if(!k(I))return;throw j.data&&(i("ERRORS."+j.data.error).then(M=>{e.addToast({title:"Error",date:new Date,body:M}),e.error=M},console.error),te(j.data.error)),U("sourceUrl","missing",!1),j}finally{y(I)}}let le=He(new Map);function Ie(){let S=new Set,I=e.details&&e.details.pullRequest;if(!I)return S;typeof I.title=="string"&&S.add(I.title),typeof I.body=="string"&&S.add(I.body),typeof I.diff=="string"&&S.add(I.diff);let j=I.comments||[];for(let M of j)typeof M.author=="string"&&S.add(M.author),typeof M.body=="string"&&S.add(M.body);return S}let _e=Q("pr",()=>{let S=Ie(),I=Array.from(S);return I.length===0?null:{contents:I,options:ee()}},(S,I)=>{if(!Array.isArray(S?.contents)||S.contents.length!==I.contents.length||S.contents.some(v=>typeof v!="string"))throw Error("Invalid preview response");let j=I.contents,M=new Map;for(let v=0;v<j.length&&v<S.contents.length;v++)M.set(j[v],S.contents[v]);le.clear(),M.forEach((v,C)=>le.set(C,v))});e.anonymizePrContent=function(S){return S&&(le.has(S)?le.get(S):"")};async function Ae(){let S=parseGithubUrl(e.sourceUrl),I=m();try{z(),e.gistOAuthRequired=!1,e.gistAppRequired=!1;let j=await t.get(`/api/gist/source/${S.gistId}`);if(!k(I))return;e.details=j.data,e.gistId||(e.gistId="gist-"+S.gistId.substring(0,6)+"-"+generateRandomId(4))}catch(j){if(!k(I))return;if(j.data?.error==="github_app_reconnect_required"){e.gistAppRequired=!0,U("sourceUrl","missing",!1);return}if(j.data?.error==="github_oauth_required"){e.gistOAuthRequired=!0,U("sourceUrl","missing",!1);return}throw j.data&&(i("ERRORS."+j.data.error).then(M=>{e.addToast({title:"Error",date:new Date,body:M}),e.error=M},console.error),te(j.data.error)),U("sourceUrl","missing",!1),j}finally{y(I)}}let Ee=He(new Map);function Pe(){let S=new Set,I=e.details&&e.details.gist;if(!I)return S;typeof I.description=="string"&&S.add(I.description),typeof I.ownerLogin=="string"&&S.add(I.ownerLogin);let j=I.files||[];for(let v of j)typeof v.filename=="string"&&S.add(v.filename),typeof v.content=="string"&&S.add(v.content);let M=I.comments||[];for(let v of M)typeof v.author=="string"&&S.add(v.author),typeof v.body=="string"&&S.add(v.body);return S}let Se=Q("gist",()=>{let S=Pe(),I=Array.from(S);return I.length===0?null:{contents:I,options:ee()}},(S,I)=>{if(!Array.isArray(S?.contents)||S.contents.length!==I.contents.length||S.contents.some(v=>typeof v!="string"))throw Error("Invalid preview response");let j=I.contents,M=new Map;for(let v=0;v<j.length&&v<S.contents.length;v++)M.set(j[v],S.contents[v]);Ee.clear(),M.forEach((v,C)=>Ee.set(C,v)),$e()});e.anonymizeGistContent=function(S){return S&&(Ee.has(S)?Ee.get(S):"")},e.previewGistFiles=[];function $e(){let S=e.details&&e.details.gist&&e.details.gist.files||[];e.previewGistFiles=S.map(I=>({filename:e.anonymizeGistContent(I.filename),content:e.anonymizeGistContent(I.content),language:I.language}))}e.watch("details.gist.files",$e,!0),e.watch("terms",$e);function Ue(){let S=e.conference;if(e.conference_data=null,!S)return;let I=e.isUpdate&&S===e._originalConference;t.get("/api/conferences/"+S).then(j=>{e.conference===S&&(e.conference_data=j.data,e.conference_data.startDate=new Date(e.conference_data.startDate),e.conference_data.endDate=new Date(e.conference_data.endDate),!I&&(e.options.expirationDate=new Date(e.conference_data.endDate),e.options.expirationMode="remove",e.options.update=e.conference_data.options.update,e.options.image=e.con Line truncated
`).filter(C=>C),connection:e.githubConnection,fullName:`${j.owner}/${j.repo}`,repository:e.sourceUrl,reconnectRepositoryId:e.reconnectRepositoryId,options:e.options,source:e.source,conference:e.conference};e.details&&(M.options.pageSource=e.details.pageSource),z();let v=e.isUpdate?"/api/repo/"+e.repoId:"/api/repo/";t.post(v,M,{headers:{"Content-Type":"application/json"}}).then(()=>{R.clear(),window.location.href="/status/"+e.repoId},C=>{C.data&&(i("ERRORS."+C.data.error).then(P=>{e.error=P},console.error),te(C.data.error))}).finally(()=>{I.disabled=!1})},e.anonymizeGist=S=>{if(e.sourceLoading){e.error="Wait for the source to finish loading before saving.";return}if(ne())return;if(e.anonymize?.invalid||e.sourceUrlError){e.error="Please correct the highlighted fields before saving.";return}let I=S.currentTarget||S.target.closest?.("button")||S.target;I.disabled=!0;let j=parseGithubUrl(e.sourceUrl),M={gistId:e.gistId,terms:e.terms.trim().split(`
`).filter(C=>C),source:{gistId:j.gistId},options:e.options,conference:e.conference};z();let v=e.isUpdate?"/api/gist/"+e.gistId:"/api/gist/";t.post(v,M,{headers:{"Content-Type":"application/json"}}).then(()=>{R.clear(),window.location.href="/gist/"+e.gistId},C=>{C.data&&(i("ERRORS."+C.data.error).then(P=>{e.error=P},console.error),te(C.data.error))}).finally(()=>{I.disabled=!1})},e.anonymizePullRequest=S=>{if(e.sourceLoading){e.error="Wait for the source to finish loading before saving.";return}if(ne())return;if(e.anonymize?.invalid||e.sourceUrlError){e.error="Please correct the highlighted fields before saving.";return}let I=S.currentTarget||S.target.closest?.("button")||S.target;I.disabled=!0;let j=parseGithubUrl(e.sourceUrl),M={pullRequestId:e.pullRequestId,connection:e.githubConnection,terms:e.terms.trim().split(`
`).filter(C=>C),source:{repositoryFullName:`${j.owner}/${j.repo}`,pullRequestId:j.pullRequestId},options:e.options,conference:e.conference};z();let v=e.isUpdate?"/api/pr/"+e.pullRequestId:"/api/pr/";t.post(v,M,{headers:{"Content-Type":"application/json"}}).then(()=>{R.clear(),window.location.href="/pr/"+e.pullRequestId},C=>{C.data&&(i("ERRORS."+C.data.error).then(P=>{e.error=P},console.error),te(C.data.error))}).finally(()=>{I.disabled=!1})},e.watch("conference",()=>{Ue()}),e.watch("terms",()=>{e.detectedType==="repo"&&ue(),e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()}),e.watch("options.image",()=>{e.detectedType==="repo"&&ue(),e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()}),e.watch("options.link",()=>{e.detectedType==="repo"&&ue(),e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()}),e.watch("details",()=>{e.detectedType==="pr"&&_e(),e.detectedType==="gist"&&Se()},!0)},sr=function(e,t,n,r,s,i){let a=ct(),u=Qt();e.on("dark-mode",(O,G)=>{e.aceOption&&(G?e.aceOption.theme="nord_dark":e.aceOption.theme="chrome")});let f=0,h=!1;e.on("dispose",()=>{h=!0,f++,g&&g.resolve()}),e.files=[],e.isMac=/Mac|iPhone|iPad|iPod/.test(navigator.platform||navigator.userAgent),e.fileSearchQuery="",e.fileSearchResults=null,e.fileSearchLoading=!1,u(document,"keydown",function(O){if((O.metaKey||O.ctrlKey)&&O.key==="k"){O.preventDefault();var G=document.querySelector(".tree-search-input");G&&(G.focus(),G.select())}});var g=null;e.onFileSearchChange=function(){g&&(g.resolve(),g=null);let O=e.fileSearchQuery;if(!O||O.length<2){e.fileSearchResults=null,e.fileSearchLoading=!1;return}e.fileSearchLoading=!0;let G=i.defer();g=G,t.get(`/api/repo/${e.repoId}/files/search?q=${encodeURIComponent(O)}`,{timeout:G.promise}).then(function(J){if(!(h||g!==G)){g=null,e.fileSearchLoading=!1;var ee={};e.files.forEach(function(z){ee[(z.path||"")+"/"+z.name]=!0});for(var Q=[],oe={},ue=0;ue<J.data.length;ue++){var pe=J.data[ue];if(pe.path)for(var le=pe.path.split("/"),Ie="",_e=0;_e<le.length;_e++){var Ae=Ie;Ie=Ie?Ie+"/"+le[_e]:le[_e];var Ee=Ae+"/"+le[_e];!ee[Ee]&&!oe[Ee]&&(oe[Ee]=!0,Q.push({name:le[_e],path:Ae}))}}Q.sort(function(z,ne){return(z.path||"").split("/").length-(ne.path||"").split("/").length}),Q.length>0&&e.files.push.apply(e.files,Q);for(var Pe=[],Se=0;Se<J.data.length;Se++){var $e=J.data[Se],Ue=($e.path||"")+"/"+$e.name;!ee[Ue]&&$e.size!=null&&(Pe.push($e),ee[Ue]=!0)}Pe.length>0&&e.files.push.apply(e.files,Pe),e.fileSearchResults=J.data}},function(){!h&&g===G&&(g=null,e.fileSearchLoading=!1,e.fileSearchResults=[])})};let k={yml:"yaml",txt:"text",py:"python",js:"javascript",ts:"typescript"},m=["license","txt"],y=["png","jpg","jpeg","gif","svg","ico","bmp","tiff","tif","webp","avif","heif","heic"],E=["wav","mp3","ogg","wma","flac","aac","m4a"],w=["mp4","avi","webm","mov","mpg","mpeg","mkv","flv","wmv","3gp","3g2","m4v","f4v","f4p","f4a","f4b"];e.on("routeUpdate",function(O,G){if(e.repoId!=r.repoId)return _();if((r.path||"")!=e.filePath){e.filePath=r.path||"",e.paths=e.filePath.split("/").filter(J=>J&&J.trim().length>0),ie();for(let J=0;J<e.paths.length-1;J++){let ee=J>0?e.paths.slice(0,J).join("/"):"";e.files.some(oe=>oe.path===ee)||e.getFiles(ee)}}});function R(){if(e.paths[0]!="")return;let O=["readme.md","readme.txt","readme.org","readme.1st","readme"],G={};for(let ee of e.files)ee.name.toLowerCase().indexOf("readme")>-1&&(G[ee.name.toLowerCase()]=ee.name);let J=null;for(let ee of O)if(G[ee]){J=ee;break}if(!J&&Object.keys(G).length>0&&(J=Object.keys(G)[0]),J){let ee=n.url();ee[ee.length-1]!="/"&&(ee+="/"),n.url(ee+encodePathForUrl(G[J]))}}e.fileCounts=null,e.getFiles=function(O){let G=e.repoId;return t.get(`/api/repo/${e.repoId}/files/?path=${encodeURIComponent(O)}&v=${e.options.lastUpdateDate}`).then(function(J){if(h||G!==e.repoId)return[];let ee=O||"";return e.files=e.files.filter(Q=>Q.path!==ee),e.files.push(...J.data),J.data},function(J){if(h||G!==e.repoId)return[];e.type="error",e.content=J&&J.data&&J.data.error||"unknown_error",e.files=[]})};function D(){let O=e.repoId;t.get(`/api/repo/${e.repoId}/files/counts`).then(function(G){h||O!==e.repoId||(e.fileCounts=G.data)},function(){e.fileCounts={}})}function A(){return e.files.filter(O=>O.name==e.paths[e.paths.length-1]&&O.path==e.paths.slice(0,e.paths.length-1).join("/"))[0]}var V=null;e.on("dispose",function(){V&&a.interval.cancel(V)});function H(O){if(h)return;let G=e.repoId;t.get(`/api/repo/${e.repoId}/options`).then(J=>{if(!(h||G!==e.repoId)){if(e.options=J.data,e.options.url){window.location=e.options.url;return}O&&O(J.data)}},J=>{if(!(h||G!==e.repoId)){var ee=J.data||{};if(ee.error==="rate_limited"&&ee.resetAt){let oe=function(){var ue=Math.max(0,Math.ceil((e.rateLimitResetAt-Date.now())/1e3));if(ue<=0)e.rateLimitCountdown="",e.rateLimitResetAt=0,V&&(a.interval.cancel(V),V=null),H(O);else{var pe=Math.floor(ue/60),le=ue%60;e.rateLimitCountdown=pe>0?pe+"m "+le+"s":le+"s"}};var Q=oe;e.type="rate_limited",e.rateLimitResetAt=ee.resetAt,V&&a.interval.canc Line truncated
`+w.join(`
`)],{type:"text/csv"}),D=document.createElement("a");D.href=URL.createObjectURL(R),D.download=r.username+"-repositories.csv",D.click()},e.showStatusMessage=m=>{let y=m.statusMessage||"(no message)";window.prompt(`Status message for ${m.repoId} (${m.status}):`,y)},e.fetchGithubInfo=m=>{let y=window.open("","_blank");y&&y.document.write("<pre>Loading GitHub info for "+m.repoId+"...</pre>"),t.get("/api/admin/repos/"+m.repoId+"/github").then(E=>{y&&(y.document.open(),y.document.write('<pre style="font:13px monospace;padding:16px;white-space:pre-wrap">'+JSON.stringify(E.data,null,2).replace(/[<>]/g,w=>w==="<"?"&lt;":"&gt;")+"</pre>"),y.document.close())},E=>{let w=E&&E.data?JSON.stringify(E.data,null,2):String(E);y&&(y.document.body.innerHTML='<pre style="color:#B42318;padding:16px">'+w+"</pre>")})};function f(m){t.get("/api/admin/users/"+m+"/repos",{}).then(y=>{e.repositories=y.data},y=>{console.error(y)})}function h(m){t.get("/api/admin/users/"+m,{}).then(y=>{e.userInfo=y.data},y=>{console.error(y)})}h(r.username),f(r.username),e.banUser=()=>{confirm(`Ban user ${r.username}?`)&&t.post(`/api/admin/users/${r.username}/ban`).then(()=>h(r.username),m=>console.error(m))},e.activateUser=()=>{t.post(`/api/admin/users/${r.username}/activate`).then(()=>h(r.username),m=>console.error(m))},e.promoteUser=()=>{confirm(`Promote ${r.username} to admin?`)&&t.post(`/api/admin/users/${r.username}/promote`).then(()=>h(r.username),m=>console.error(m))},e.demoteUser=()=>{confirm(`Remove admin privileges from ${r.username}?`)&&t.post(`/api/admin/users/${r.username}/demote`).then(()=>h(r.username),m=>console.error(m))},e.tokens=[],e.tokenForm={name:"",plaintext:null};function v(){t.get("/api/admin/tokens").then(m=>{e.tokens=m.data||[]},m=>{m.status!==401&&m.status!==403&&console.error(m)})}v(),e.createToken=()=>{e.tokenForm.name&&t.post("/api/admin/tokens",{name:e.tokenForm.name}).then(m=>{e.tokenForm.plaintext=m.data.token,e.tokenForm.name="",v()},m=>console.error(m))},e.revokeToken=m=>{confirm(`Revoke token "${m.name}"?`)&&t.delete("/api/admin/tokens/"+m.id).then(()=>v(),y=>console.error(y))},e.removeCache=m=>{confirm("Remove cached files for "+m.repoId+"?")&&t.delete("/api/admin/repos/"+m.repoId).then(()=>f(r.username),y=>console.error(y))},e.removeRepository=m=>{confirm("Remove repository "+m.repoId+"?")&&t.delete("/api/repo/"+m.repoId+"/").then(()=>f(r.username),y=>console.error(y))},e.updateRepository=m=>{let y=He({title:`Refreshing ${m.repoId}...`,date:new Date,body:`The repository ${m.repoId} is going to be refreshed.`});e.toasts.push(y),t.post(`/api/repo/${m.repoId}/refresh`).then(E=>{E.data.status=="ready"?y.title=`${m.repoId} is refreshed.`:y.title=`Refreshing of ${m.repoId}.`},E=>{y.title=`Error during the refresh of ${m.repoId}.`,y.body=E.body})},e.getGitHubRepositories=m=>{t.get(`/api/user/${e.userInfo.username}/all_repositories`,{params:{force:"1"}}).then(y=>{e.userInfo.repositories=y.data})};let k=null;e.watch("query",()=>{n.timeout.cancel(k),k=n.timeout(()=>{f(r.username)},500)},!0)},tl=function(e,t,s){let r=ct(),n=Jt();e.Math=Math,e.watch("user.status",()=>{e.user==null&&s.url("/")}),e.user==null&&s.url("/"),e.conferences=[],e.total=-1,e.totalPage=0,e.statusCounts=[];let i=E=>{if(E.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){E.preventDefault();let w=document.querySelector('.admin-filter-toolbar input[type="search"]');w&&w.focus()}};n(document,"keydown",i),e.on("dispose",()=>document.removeEventListener("keydown",i)),e.clearFilter=E=>{E==="dateRange"?(e.query.dateFrom="",e.query.dateTo=""):e.query[E]="",e.query.page=1},e.chips=[];let a=()=>{let E=[];(e.query.dateFrom||e.query.dateTo)&&E.push({key:"dateRange",label:"Date",value:(e.query.dateFrom||"\u2026")+" \u2013 "+(e.query.dateTo||"\u2026")}),e.chips=E};e.statusCountFor=E=>{let w=(e.statusCounts||[]).find(R=>R._id===E);return w?w.count:0},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=E=>{e.query.sort===E?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=E,e.query.direction="desc"),e.query.page=1},e.sortIcon=E=>e.query.sort===E?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let u="admin.conferences.filterPrefs",f={page:1,limit:25,sort:"name",direction:"asc",search:"",dateFrom:"",dateTo:"",ready:!1,expired:!1,removed:!1,error:!0,preparing:!0},h=loadFilterPrefs(u)||{};e.query=Object.assign({},f,h,{page:1,search:""});let v=s.search();v.search&&(e.query.search=v.search);let k="admin.conferences.presets";e.presets=JSON.parse(localStorage.getItem(k)||"[]"),e.savePreset=()=>{let E=window.prompt("Preset name:");if(!E)return;let w=Object.assign({},e.query);delete w.page,e.presets=(e.presets||[]).filter(R=>R.name!==E),e.presets.push({name:E,query:w}),localStorage.setItem(k,JSON.stringify(e.presets))},e.applyPreset=E=>{Object.assign(e.query,E.query,{page:1})},e.deletePreset=E=>{e.presets=(e.presets||[]).filter(w=>w.name!==E.name),localStorage.setItem(k,JS Line truncated
`)],{type:"text/csv"}),D=document.createElement("a");D.href=URL.createObjectURL(R),D.download=r.username+"-repositories.csv",D.click()},e.showStatusMessage=m=>{let y=m.statusMessage||"(no message)";window.prompt(`Status message for ${m.repoId} (${m.status}):`,y)},e.fetchGithubInfo=m=>{let y=window.open("","_blank");y&&y.document.write("<pre>Loading GitHub info for "+m.repoId+"...</pre>"),t.get("/api/admin/repos/"+m.repoId+"/github").then(E=>{y&&(y.document.open(),y.document.write('<pre style="font:13px monospace;padding:16px;white-space:pre-wrap">'+JSON.stringify(E.data,null,2).replace(/[<>]/g,w=>w==="<"?"&lt;":"&gt;")+"</pre>"),y.document.close())},E=>{let w=E&&E.data?JSON.stringify(E.data,null,2):String(E);y&&(y.document.body.innerHTML='<pre style="color:#B42318;padding:16px">'+w+"</pre>")})};function f(m){t.get("/api/admin/users/"+m+"/repos",{}).then(y=>{e.repositories=y.data},y=>{console.error(y)})}function h(m){t.get("/api/admin/users/"+m,{}).then(y=>{e.userInfo=y.data},y=>{console.error(y)})}h(r.username),f(r.username),e.banUser=()=>{confirm(`Ban user ${r.username}?`)&&t.post(`/api/admin/users/${r.username}/ban`).then(()=>h(r.username),m=>console.error(m))},e.activateUser=()=>{t.post(`/api/admin/users/${r.username}/activate`).then(()=>h(r.username),m=>console.error(m))},e.promoteUser=()=>{confirm(`Promote ${r.username} to admin?`)&&t.post(`/api/admin/users/${r.username}/promote`).then(()=>h(r.username),m=>console.error(m))},e.demoteUser=()=>{confirm(`Remove admin privileges from ${r.username}?`)&&t.post(`/api/admin/users/${r.username}/demote`).then(()=>h(r.username),m=>console.error(m))},e.tokens=[],e.tokenForm={name:"",plaintext:null};function g(){t.get("/api/admin/tokens").then(m=>{e.tokens=m.data||[]},m=>{m.status!==401&&m.status!==403&&console.error(m)})}g(),e.createToken=()=>{e.tokenForm.name&&t.post("/api/admin/tokens",{name:e.tokenForm.name}).then(m=>{e.tokenForm.plaintext=m.data.token,e.tokenForm.name="",g()},m=>console.error(m))},e.revokeToken=m=>{confirm(`Revoke token "${m.name}"?`)&&t.delete("/api/admin/tokens/"+m.id).then(()=>g(),y=>console.error(y))},e.removeCache=m=>{confirm("Remove cached files for "+m.repoId+"?")&&t.delete("/api/admin/repos/"+m.repoId).then(()=>f(r.username),y=>console.error(y))},e.removeRepository=m=>{confirm("Remove repository "+m.repoId+"?")&&t.delete("/api/repo/"+m.repoId+"/").then(()=>f(r.username),y=>console.error(y))},e.updateRepository=m=>{let y=He({title:`Refreshing ${m.repoId}...`,date:new Date,body:`The repository ${m.repoId} is going to be refreshed.`});e.toasts.push(y),t.post(`/api/repo/${m.repoId}/refresh`).then(E=>{E.data.status=="ready"?y.title=`${m.repoId} is refreshed.`:y.title=`Refreshing of ${m.repoId}.`},E=>{y.title=`Error during the refresh of ${m.repoId}.`,y.body=E.body})},e.getGitHubRepositories=m=>{t.get(`/api/user/${e.userInfo.username}/all_repositories`,{params:{force:"1"}}).then(y=>{e.userInfo.repositories=y.data})};let k=null;e.watch("query",()=>{s.timeout.cancel(k),k=s.timeout(()=>{f(r.username)},500)},!0)},ol=function(e,t,n){let r=ct(),s=Qt();e.Math=Math,e.watch("user.status",()=>{e.user==null&&n.url("/")}),e.user==null&&n.url("/"),e.conferences=[],e.total=-1,e.totalPage=0,e.statusCounts=[];let i=E=>{if(E.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){E.preventDefault();let w=document.querySelector('.admin-filter-toolbar input[type="search"]');w&&w.focus()}};s(document,"keydown",i),e.on("dispose",()=>document.removeEventListener("keydown",i)),e.clearFilter=E=>{E==="dateRange"?(e.query.dateFrom="",e.query.dateTo=""):e.query[E]="",e.query.page=1},e.chips=[];let a=()=>{let E=[];(e.query.dateFrom||e.query.dateTo)&&E.push({key:"dateRange",label:"Date",value:(e.query.dateFrom||"\u2026")+" \u2013 "+(e.query.dateTo||"\u2026")}),e.chips=E};e.statusCountFor=E=>{let w=(e.statusCounts||[]).find(R=>R._id===E);return w?w.count:0},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=E=>{e.query.sort===E?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=E,e.query.direction="desc"),e.query.page=1},e.sortIcon=E=>e.query.sort===E?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let u="admin.conferences.filterPrefs",f={page:1,limit:25,sort:"name",direction:"asc",search:"",dateFrom:"",dateTo:"",ready:!1,expired:!1,removed:!1,error:!0,preparing:!0},h=loadFilterPrefs(u)||{};e.query=Object.assign({},f,h,{page:1,search:""});let g=n.search();g.search&&(e.query.search=g.search);let k="admin.conferences.presets";e.presets=JSON.parse(localStorage.getItem(k)||"[]"),e.savePreset=()=>{let E=window.prompt("Preset name:");if(!E)return;let w=Object.assign({},e.query);delete w.page,e.presets=(e.presets||[]).filter(R=>R.name!==E),e.presets.push({name:E,query:w}),localStorage.setItem(k,JSON.stringify(e.presets))},e.applyPreset=E=>{Object.assign(e.query,E.query,{page:1})},e.deletePreset=E=>{e.presets=(e.presets||[]).filter(w=>w.name!==E.name),localStorage.setItem(k,JS Line truncated
`).map((ue,pe)=>pe===0?ue:oe+ue).join(`
`)}else typeof C=="number"||typeof C=="boolean"?Q=String(C):Q=JSON.stringify(C);ae.push(`${J}${Q}${ee}`)}),ae.push("}"),ae.join(`
`)}function h(D){let A=[],P="",H=/(\w+):(>=|<=|!=|>|<|=)?([^\s]+)/g,q=0,U;for(;U=H.exec(D);)P+=D.slice(q,U.index),q=H.lastIndex,A.push({key:U[1],op:U[2]||"=",val:U[3]});return P+=D.slice(q),{filters:A,free:P.trim().toLowerCase()}}function v(D,A){for(let P of A.filters){let H=(U,ae,ie)=>{let C=parseFloat(U),O=parseFloat(ae);return ie==="="?String(U)===String(ae):ie==="!="?String(U)!==String(ae):ie===">="?C>=O:ie==="<="?C<=O:ie===">"?C>O:ie==="<"?C<O:!0},q;if(P.key==="code")q=D.displayMessage;else if(P.key==="module")q=D.module;else if(P.key==="status")q=D._status;else if(P.key==="url")q=D._url;else if(P.key==="repo")q=D._repoId;else if(P.key==="level")q=D.level;else continue;if(q==null||!H(q,P.val,P.op))return!1}return!(A.free&&((D.displayMessage||"")+" "+(D.module||"")+" "+(D._url||"")+" "+JSON.stringify(D.raw||[])).toLowerCase().indexOf(A.free)===-1)}function k(){let D=h(e.query.search||"");e.parsedFilterCount=D.filters.length;let A=e.query.bucket,P=e.entries.filter(q=>A&&q._bucket!==A?!1:v(q,D)),H=e.query.group;if(H){let q=ie=>H==="module"?ie.module:ie.displayMessage||ie.message||"_",U=new Map;for(let ie of P){let C=q(ie);if(U.has(C)){let O=U.get(C);O.count++,O._related.push(ie),new Date(ie.ts)>new Date(O.ts)&&(O.ts=ie.ts,O._url=ie._url,O._status=ie._status),new Date(ie.ts)<new Date(O._firstSeen)&&(O._firstSeen=ie.ts)}else{let O=Object.assign({},ie);O._key=`${H}:${C}`,O._related=[ie],O._firstSeen=ie.ts,O._lastHourCount=0,O.count=1,U.set(C,O)}}let ae=Date.now()-3600*1e3;for(let ie of U.values())ie._lastHourCount=ie._related.filter(C=>new Date(C.ts).getTime()>=ae).length;P=Array.from(U.values())}else P=P.map((q,U)=>(q._key="row:"+U+":"+q.ts,q._related=[q],q._firstSeen=q.ts,q._lastHourCount=0,q.count=1,q));e.query.sort==="count"?P.sort((q,U)=>U.count-q.count||new Date(U.ts)-new Date(q.ts)):P.sort((q,U)=>new Date(U.ts)-new Date(q.ts)),e.visible=P}function m(D){let A=D?e.entries.length:0,P=D?e.pageSize:Math.max(e.pageSize,e.entries.length||e.pageSize);t.get("/api/admin/errors",{params:{offset:A,limit:P}}).then(H=>{let q=(H.data.entries||[]).map(u);e.entries=D?e.entries.concat(q):q,e.available=!!H.data.available,e.cap=H.data.max||e.cap,e.total=H.data.total||e.entries.length,k()},H=>console.error(H))}e.loadMore=()=>m(!0),e.canLoadMore=()=>e.entries.length<e.total;function y(){t.get("/api/admin/errors/stats").then(D=>{let A=D.data||{},P=A.prev24h?Math.round((A.last24h-A.prev24h)/A.prev24h*100):0;e.stats={last24h:A.last24h||0,prev24h:A.prev24h||0,delta:P,severity:A.severity||{error:0,warn:0,info:0},unique:A.unique||{error:0,warn:0,info:0},buckets:A.buckets||[],dropped:A.dropped||0}},D=>console.error(D))}function E(){m(),y()}e.barPx=(D,A)=>{let P=e.stats.buckets||[],H=0;for(let ie of P)H=Math.max(H,(ie.error||0)+(ie.warn||0)+(ie.info||0));if(!H)return 0;let q=(D.error||0)+(D.warn||0)+(D.info||0);if(!q)return 0;let U=Math.round(q/H*60),ae=D[A]||0;return Math.round(ae/q*U)},e.bucketTitle=D=>`${new Date(D.hour).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})} \xB7 ${D.error||0} err \xB7 ${D.warn||0} warn \xB7 ${D.info||0} info`,e.toggle=D=>{e.expanded[D._key]=!e.expanded[D._key]},e.setBucket=D=>{e.query.bucket=D},e.refreshNow=E,e.clearAll=()=>{confirm("Clear all captured errors?")&&t.delete("/api/admin/errors").then(E,D=>console.error(D))},e.exportCsv=()=>{let D=["ts","level","module","displayMessage","_status","_url","_repoId"],A=[D.join(",")];for(let U of e.visible)A.push(D.map(ae=>{let ie=U[ae]==null?"":String(U[ae]);return/[",\n]/.test(ie)?`"${ie.replace(/"/g,'""')}"`:ie}).join(","));let P=new Blob([A.join(`
`)],{type:"text/csv;charset=utf-8"}),H=URL.createObjectURL(P),q=document.createElement("a");q.href=H,q.download=`errors-${new Date().toISOString().slice(0,19)}.csv`,document.body.appendChild(q),q.click(),document.body.removeChild(q),URL.revokeObjectURL(H)};function w(D){e.copyHint=`${D} copied`,n.timeout(()=>{e.copyHint=""},1500)}e.copyJson=D=>{navigator.clipboard.writeText(D._detailJson).then(()=>w("JSON"))},e.copyCurl=D=>{if(!D._url)return;let P=`curl -X ${D._method||"GET"} '${window.location.origin}${D._url}'`;navigator.clipboard.writeText(P).then(()=>w("curl"))},E();let R=r(()=>{e.query.autoRefresh&&E()},15e3);e.on("dispose",()=>r.cancel(R)),e.watch("query.search",k),e.watch("query.bucket",k),e.watch("query.sort",k),e.watch("query.group",k)},nl=function(e,t,s,r){if(e.Math=Math,e.watch("user.status",()=>{e.user==null&&s.url("/")}),e.user==null){s.url("/");return}e.data=null,e.loading=!0,e.error=null,e.performance=null,e.performanceMinutes=15,e.performanceTab="routes",e.performanceError=null,e.latencyBound=function(y,E){return y.count?y[E]==null?"> 60 s":"\u2264 "+y[E]+" ms":"No samples"},e.firstByteBound=function(y){return e.latencyBound({count:y.firstByteCount,p95UpperMs:y.firstByteP95UpperMs},"p95UpperMs")},e.sampleAge=function(y){return Math.max(0,Math.round((Date.now()-y.sampledAt)/1e3))},e.peakRss=function(y){return(e.performance.runtimeSeries||[]).filter(function(E){return E.instance===y.instance}).reduce(function(E,w){return Math.max(E,w.rss)},y.memory.rss)},e.performanceWarning=function(y){return e.sampleAge(y)>45||y.droppedBatches||y.droppedMetrics||y.sockets?.closeWait>0||y.eventLoop.p95Ms>100||y.memoryLimitBytes&&y.memory.rss>y.memoryLimitBytes*.8};var n=!1;e.loadPerformance=function(){var y=e.performanceMinutes;e.performance?.windowMinutes&&e.performance.windowMinutes!==y&&(e.performance=null),!n&&(n=!0,t.get("/api/admin/performance",{params:{minutes:y}}).then(function(E){y===e.performanceMinutes&&(e.performance=E.data,e.performanceError=E.data.available?null:"Performance monitoring is unavailable. Request history may be incomplete.")},function(){y===e.performanceMinutes&&(e.performanceError="Could not refresh performance monitoring. Displayed samples may be stale.")}).finally(function(){n=!1,y!==e.performanceMinutes&&e.loadPerformance()}))};function i(y){if(y==null)return"\u2014";for(var E=["B","KB","MB","GB","TB"],w=0,R=y;R>=1024&&w<E.length-1;)R/=1024,w++;return R.toFixed(w>0?1:0)+" "+E[w]}e.humanBytes=i;function a(y){if(!y)return"\u2014";var E=Math.floor(y/86400),w=Math.floor(y%86400/3600),R=Math.floor(y%3600/60);return E>0?E+"d "+(w<10?"0":"")+w+"h":w>0?w+"h "+(R<10?"0":"")+R+"m":R+"m"}e.humanDuration=a;function u(y){return y==null?"\u2014":y>=1e6?(y/1e6).toFixed(1)+"M":y>=1e3?(y/1e3).toFixed(1)+"K":String(y)}e.humanNum=u,e.queueTotal=function(y){return y?(y.waiting||0)+(y.active||0)+(y.delayed||0)+(y.failed||0):0},e.statusCount=function(y){if(!e.data||!e.data.repos)return 0;for(var E=e.data.repos.statusBreakdown||[],w=0;w<E.length;w++)if(E[w]._id===y)return E[w].count;return 0},e.barPct=function(y){var E=e.data&&e.data.repos?e.data.repos.total:0;if(!E)return 0;var w=[y];y==="expired"&&w.push("expiring"),y==="removed"&&w.push("removing"),y==="preparing"&&w.push("download");var R=0;return w.forEach(function(D){R+=e.statusCount(D)}),Math.max(.4,R/E*100)},e.errPct=function(y){if(!e.data||!e.data.errors)return 0;var E=Math.max(e.data.errors.severity.error,e.data.errors.severity.warn,e.data.errors.severity.info,1);return e.data.errors.severity[y]/E*100};function f(y){var E=y||[];return E.map(function(w,R){var D=E[R-1]||{},A=Object.assign({},w);return A.dailyRepositories=R?Math.max(0,(w.nbRepositories||0)-(D.nbRepositories||0)):0,A.dailyUsers=R?Math.max(0,(w.nbUsers||0)-(D.nbUsers||0)):0,A.dailyPageViews=R?Math.max(0,(w.nbPageViews||0)-(D.nbPageViews||0)):0,A})}function h(y){var E=y&&y.length?y[y.length-1]:{};return{repositories:E.dailyRepositories||0,users:E.dailyUsers||0,pageViews:E.dailyPageViews||0}}var v={};e.historyBarH=function(y,E){return!y||!v[E]?0:Math.max(1,Math.round(y[E]/v[E]*140))},e.historyLabel=function(y){if(!y||!y.date)return"";var E=new Date(y.date);return E.getUTCMonth()+1+"/"+E.getUTCDate()};function k(){e.loadPerformance(),t.get("/api/admin/overview").then(function(y){y.data.history=f(y.data.history),y.data.daily={today:h(y.data.history)},e.data=y.data,e.loading=!1,e.error=null,v={},(y.data.history||[]).forEach(function(E){["dailyPageViews","dailyRepositories","dailyUsers","nbUsers"].forEach(function(w){(!v[w]||E[w]>v[w])&&(v[w]=E[w])})})},function(y){e.loading=!1,e.error=y.data&&y.data.error||"Failed to load overview"})}k();var m=r(k,3e4);e.on("dispose",function(){r.cancel(m)})};var rl=[{path:"/connections",template:"partials/connections.htm",title:"GitHub connections \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Qa(e,t.http)},{path:"/signin",template:"partials/signin.htm",title:"Sign in \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Xa(e,t.http)} Line truncated
anonymous_github`)])])])])]),p(" Limitations "),o("h4",{class:"faq-cat"},[o("svg",{class:"ui-icon mr-2","aria-hidden":"true"},[o("use",{href:"#ui-icon-warning"})]),b("Limitations ")]),o("div",{class:"panel-group",id:"faq-limitations",role:"tablist","aria-multiselectable":"true"},[o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingLimitation"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-limitations",href:"#limitation","aria-expanded":"false","aria-controls":"limitation"}," What are the limitations of Anonymous GitHub? ")])]),o("div",{id:"limitation",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingLimitation"},[o("div",{class:"panel-body p-3"},[o("ul",null,[o("li",null," Anonymous GitHub only anonymizes text-based files. Binary files (compiled executables, archives, etc.) are served as-is without anonymization. "),o("li",null," Files larger than 8 MB are not supported. "),o("li",null," Static site generators (such as Jekyll) used with GitHub Pages are not fully supported, although Markdown files are converted to HTML. "),o("li",null," The anonymization of terms within source code may change the behavior of the program (e.g., if a replaced term appears in a string literal or identifier). ")])])])])]),p(" Privacy & Security "),o("h4",{class:"faq-cat"},[o("svg",{class:"ui-icon mr-2","aria-hidden":"true"},[o("use",{href:"#ui-icon-shield"})]),b("Privacy & Security ")]),o("div",{class:"panel-group",id:"faq-privacy",role:"tablist","aria-multiselectable":"true"},[o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingData"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#data","aria-expanded":"false","aria-controls":"data"}," How is my data handled? ")])]),o("div",{id:"data",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingData"},[o("div",{class:"panel-body p-3"},[o("p",null," Data stored on Anonymous GitHub is never shared or used for any purpose beyond providing the anonymization service. When a repository is removed or expires, only its configuration is retained \u2014 this makes it easy to restore the repository if needed and ensures that no future repository reuses the same ID. ")])])]),o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingPermissions"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#permissions","aria-expanded":"false","aria-controls":"permissions"}," Why does GitHub say Anonymous GitHub asks for write access? ")])]),o("div",{id:"permissions",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingPermissions"},[o("div",{class:"panel-body p-3"},[o("p",null,[b("The GitHub App has read-only access to the repositories you choose. You can change repository access on your "),o("a",{href:"/connections"},"GitHub connections"),b(" page.")]),o("p",null,"Legacy GitHub OAuth includes write access to private repositories because GitHub's OAuth scopes do not offer a read-only option for them. Existing OAuth anonymizations and gists can still use this connection."),o("p",null,"Anonymous GitHub reads repository content. It never pushes, modifies, or deletes files on GitHub.")])])]),o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingViewer"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#viewer","aria-expanded":"false","aria-controls":"viewer"}," Can repository owners see who viewed their anonymized repository? ")])]),o("div",{id:"viewer",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingViewer"},[o("div",{class:"panel-body p-3"},[o("p",null," No. Only the total number of views is tracked as an incremental counter. It is not possible for the repository owner or for Anonymous GitHub to identify individual viewers. Reviewer anonymity is fully preserved. ")])])])]),p(" Self-Hosting "),o("h4",{class:"faq-cat"},[o("svg",{class:"ui-icon mr-2","aria-hidden":"true"},[o("use",{href:"#project-icon-storage"})]),b("Self-Hosting ")]),o("div",{class:"panel-group",id:"faq-hosting",role:"tablist","aria-multiselectable":"true"},[o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingDeploy"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-hosting",href:"#deploy","aria-expanded":"false","aria-controls":"deploy"}," Can I deploy my own instance? ")])]),o("div",{id:"deploy",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingDeploy"},[o("div",{class:"panel-body p-3"},[o("p",null,[b(" Yes. Anonymous Git Line truncated
`)}else typeof _=="number"||typeof _=="boolean"?Q=String(_):Q=JSON.stringify(_);ae.push(`${J}${Q}${ee}`)}),ae.push("}"),ae.join(`
`)}function h(D){let A=[],V="",H=/(\w+):(>=|<=|!=|>|<|=)?([^\s]+)/g,q=0,U;for(;U=H.exec(D);)V+=D.slice(q,U.index),q=H.lastIndex,A.push({key:U[1],op:U[2]||"=",val:U[3]});return V+=D.slice(q),{filters:A,free:V.trim().toLowerCase()}}function g(D,A){for(let V of A.filters){let H=(U,ae,ie)=>{let _=parseFloat(U),O=parseFloat(ae);return ie==="="?String(U)===String(ae):ie==="!="?String(U)!==String(ae):ie===">="?_>=O:ie==="<="?_<=O:ie===">"?_>O:ie==="<"?_<O:!0},q;if(V.key==="code")q=D.displayMessage;else if(V.key==="module")q=D.module;else if(V.key==="status")q=D._status;else if(V.key==="url")q=D._url;else if(V.key==="repo")q=D._repoId;else if(V.key==="level")q=D.level;else continue;if(q==null||!H(q,V.val,V.op))return!1}return!(A.free&&((D.displayMessage||"")+" "+(D.module||"")+" "+(D._url||"")+" "+JSON.stringify(D.raw||[])).toLowerCase().indexOf(A.free)===-1)}function k(){let D=h(e.query.search||"");e.parsedFilterCount=D.filters.length;let A=e.query.bucket,V=e.entries.filter(q=>A&&q._bucket!==A?!1:g(q,D)),H=e.query.group;if(H){let q=ie=>H==="module"?ie.module:ie.displayMessage||ie.message||"_",U=new Map;for(let ie of V){let _=q(ie);if(U.has(_)){let O=U.get(_);O.count++,O._related.push(ie),new Date(ie.ts)>new Date(O.ts)&&(O.ts=ie.ts,O._url=ie._url,O._status=ie._status),new Date(ie.ts)<new Date(O._firstSeen)&&(O._firstSeen=ie.ts)}else{let O=Object.assign({},ie);O._key=`${H}:${_}`,O._related=[ie],O._firstSeen=ie.ts,O._lastHourCount=0,O.count=1,U.set(_,O)}}let ae=Date.now()-3600*1e3;for(let ie of U.values())ie._lastHourCount=ie._related.filter(_=>new Date(_.ts).getTime()>=ae).length;V=Array.from(U.values())}else V=V.map((q,U)=>(q._key="row:"+U+":"+q.ts,q._related=[q],q._firstSeen=q.ts,q._lastHourCount=0,q.count=1,q));e.query.sort==="count"?V.sort((q,U)=>U.count-q.count||new Date(U.ts)-new Date(q.ts)):V.sort((q,U)=>new Date(U.ts)-new Date(q.ts)),e.visible=V}function m(D){let A=D?e.entries.length:0,V=D?e.pageSize:Math.max(e.pageSize,e.entries.length||e.pageSize);t.get("/api/admin/errors",{params:{offset:A,limit:V}}).then(H=>{let q=(H.data.entries||[]).map(u);e.entries=D?e.entries.concat(q):q,e.available=!!H.data.available,e.cap=H.data.max||e.cap,e.total=H.data.total||e.entries.length,k()},H=>console.error(H))}e.loadMore=()=>m(!0),e.canLoadMore=()=>e.entries.length<e.total;function y(){t.get("/api/admin/errors/stats").then(D=>{let A=D.data||{},V=A.prev24h?Math.round((A.last24h-A.prev24h)/A.prev24h*100):0;e.stats={last24h:A.last24h||0,prev24h:A.prev24h||0,delta:V,severity:A.severity||{error:0,warn:0,info:0},unique:A.unique||{error:0,warn:0,info:0},buckets:A.buckets||[],dropped:A.dropped||0}},D=>console.error(D))}function E(){m(),y()}e.barPx=(D,A)=>{let V=e.stats.buckets||[],H=0;for(let ie of V)H=Math.max(H,(ie.error||0)+(ie.warn||0)+(ie.info||0));if(!H)return 0;let q=(D.error||0)+(D.warn||0)+(D.info||0);if(!q)return 0;let U=Math.round(q/H*60),ae=D[A]||0;return Math.round(ae/q*U)},e.bucketTitle=D=>`${new Date(D.hour).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})} \xB7 ${D.error||0} err \xB7 ${D.warn||0} warn \xB7 ${D.info||0} info`,e.toggle=D=>{e.expanded[D._key]=!e.expanded[D._key]},e.setBucket=D=>{e.query.bucket=D},e.refreshNow=E,e.clearAll=()=>{confirm("Clear all captured errors?")&&t.delete("/api/admin/errors").then(E,D=>console.error(D))},e.exportCsv=()=>{let D=["ts","level","module","displayMessage","_status","_url","_repoId"],A=[D.join(",")];for(let U of e.visible)A.push(D.map(ae=>{let ie=U[ae]==null?"":String(U[ae]);return/[",\n]/.test(ie)?`"${ie.replace(/"/g,'""')}"`:ie}).join(","));let V=new Blob([A.join(`
`)],{type:"text/csv;charset=utf-8"}),H=URL.createObjectURL(V),q=document.createElement("a");q.href=H,q.download=`errors-${new Date().toISOString().slice(0,19)}.csv`,document.body.appendChild(q),q.click(),document.body.removeChild(q),URL.revokeObjectURL(H)};function w(D){e.copyHint=`${D} copied`,s.timeout(()=>{e.copyHint=""},1500)}e.copyJson=D=>{navigator.clipboard.writeText(D._detailJson).then(()=>w("JSON"))},e.copyCurl=D=>{if(!D._url)return;let V=`curl -X ${D._method||"GET"} '${window.location.origin}${D._url}'`;navigator.clipboard.writeText(V).then(()=>w("curl"))},E();let R=r(()=>{e.query.autoRefresh&&E()},15e3);e.on("dispose",()=>r.cancel(R)),e.watch("query.search",k),e.watch("query.bucket",k),e.watch("query.sort",k),e.watch("query.group",k)},rl=function(e,t,n,r){if(e.Math=Math,e.watch("user.status",()=>{e.user==null&&n.url("/")}),e.user==null){n.url("/");return}e.data=null,e.loading=!0,e.error=null,e.performance=null,e.performanceMinutes=15,e.performanceTab="routes",e.performanceError=null,e.latencyBound=function(y,E){return y.count?y[E]==null?"> 60 s":"\u2264 "+y[E]+" ms":"No samples"},e.firstByteBound=function(y){return e.latencyBound({count:y.firstByteCount,p95UpperMs:y.firstByteP95UpperMs},"p95UpperMs")},e.sampleAge=function(y){return Math.max(0,Math.round((Date.now()-y.sampledAt)/1e3))},e.peakRss=function(y){return(e.performance.runtimeSeries||[]).filter(function(E){return E.instance===y.instance}).reduce(function(E,w){return Math.max(E,w.rss)},y.memory.rss)},e.performanceWarning=function(y){return e.sampleAge(y)>45||y.droppedBatches||y.droppedMetrics||y.sockets?.closeWait>0||y.eventLoop.p95Ms>100||y.memoryLimitBytes&&y.memory.rss>y.memoryLimitBytes*.8};var s=!1;e.loadPerformance=function(){var y=e.performanceMinutes;e.performance?.windowMinutes&&e.performance.windowMinutes!==y&&(e.performance=null),!s&&(s=!0,t.get("/api/admin/performance",{params:{minutes:y}}).then(function(E){y===e.performanceMinutes&&(e.performance=E.data,e.performanceError=E.data.available?null:"Performance monitoring is unavailable. Request history may be incomplete.")},function(){y===e.performanceMinutes&&(e.performanceError="Could not refresh performance monitoring. Displayed samples may be stale.")}).finally(function(){s=!1,y!==e.performanceMinutes&&e.loadPerformance()}))};function i(y){if(y==null)return"\u2014";for(var E=["B","KB","MB","GB","TB"],w=0,R=y;R>=1024&&w<E.length-1;)R/=1024,w++;return R.toFixed(w>0?1:0)+" "+E[w]}e.humanBytes=i;function a(y){if(!y)return"\u2014";var E=Math.floor(y/86400),w=Math.floor(y%86400/3600),R=Math.floor(y%3600/60);return E>0?E+"d "+(w<10?"0":"")+w+"h":w>0?w+"h "+(R<10?"0":"")+R+"m":R+"m"}e.humanDuration=a;function u(y){return y==null?"\u2014":y>=1e6?(y/1e6).toFixed(1)+"M":y>=1e3?(y/1e3).toFixed(1)+"K":String(y)}e.humanNum=u,e.queueTotal=function(y){return y?(y.waiting||0)+(y.active||0)+(y.delayed||0)+(y.failed||0):0},e.statusCount=function(y){if(!e.data||!e.data.repos)return 0;for(var E=e.data.repos.statusBreakdown||[],w=0;w<E.length;w++)if(E[w]._id===y)return E[w].count;return 0},e.barPct=function(y){var E=e.data&&e.data.repos?e.data.repos.total:0;if(!E)return 0;var w=[y];y==="expired"&&w.push("expiring"),y==="removed"&&w.push("removing"),y==="preparing"&&w.push("download");var R=0;return w.forEach(function(D){R+=e.statusCount(D)}),Math.max(.4,R/E*100)},e.errPct=function(y){if(!e.data||!e.data.errors)return 0;var E=Math.max(e.data.errors.severity.error,e.data.errors.severity.warn,e.data.errors.severity.info,1);return e.data.errors.severity[y]/E*100};function f(y){var E=y||[];return E.map(function(w,R){var D=E[R-1]||{},A=Object.assign({},w);return A.dailyRepositories=R?Math.max(0,(w.nbRepositories||0)-(D.nbRepositories||0)):0,A.dailyUsers=R?Math.max(0,(w.nbUsers||0)-(D.nbUsers||0)):0,A.dailyPageViews=R?Math.max(0,(w.nbPageViews||0)-(D.nbPageViews||0)):0,A})}function h(y){var E=y&&y.length?y[y.length-1]:{};return{repositories:E.dailyRepositories||0,users:E.dailyUsers||0,pageViews:E.dailyPageViews||0}}var g={};e.historyBarH=function(y,E){return!y||!g[E]?0:Math.max(1,Math.round(y[E]/g[E]*140))},e.historyLabel=function(y){if(!y||!y.date)return"";var E=new Date(y.date);return E.getUTCMonth()+1+"/"+E.getUTCDate()};function k(){e.loadPerformance(),t.get("/api/admin/overview").then(function(y){y.data.history=f(y.data.history),y.data.daily={today:h(y.data.history)},e.data=y.data,e.loading=!1,e.error=null,g={},(y.data.history||[]).forEach(function(E){["dailyPageViews","dailyRepositories","dailyUsers","nbUsers"].forEach(function(w){(!g[w]||E[w]>g[w])&&(g[w]=E[w])})})},function(y){e.loading=!1,e.error=y.data&&y.data.error||"Failed to load overview"})}k();var m=r(k,3e4);e.on("dispose",function(){r.cancel(m)})};var il=[{path:"/connections",template:"partials/connections.htm",title:"GitHub connections \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Xa(e,t.http)},{path:"/signin",template:"partials/signin.htm",title:"Sign in \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Za(e,t.http)} Line truncated
anonymous_github`)])])])])]),p(" Limitations "),o("h4",{class:"faq-cat"},[o("svg",{class:"ui-icon mr-2","aria-hidden":"true"},[o("use",{href:"#ui-icon-warning"})]),b("Limitations ")]),o("div",{class:"panel-group",id:"faq-limitations",role:"tablist","aria-multiselectable":"true"},[o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingLimitation"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-limitations",href:"#limitation","aria-expanded":"false","aria-controls":"limitation"}," What are the limitations of Anonymous GitHub? ")])]),o("div",{id:"limitation",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingLimitation"},[o("div",{class:"panel-body p-3"},[o("ul",null,[o("li",null," Anonymous GitHub only anonymizes text-based files. Binary files (compiled executables, archives, etc.) are served as-is without anonymization. "),o("li",null," Files larger than 8 MB are not supported. "),o("li",null," Static site generators (such as Jekyll) used with GitHub Pages are not fully supported, although Markdown files are converted to HTML. "),o("li",null," The anonymization of terms within source code may change the behavior of the program (e.g., if a replaced term appears in a string literal or identifier). ")])])])])]),p(" Privacy & Security "),o("h4",{class:"faq-cat"},[o("svg",{class:"ui-icon mr-2","aria-hidden":"true"},[o("use",{href:"#ui-icon-shield"})]),b("Privacy & Security ")]),o("div",{class:"panel-group",id:"faq-privacy",role:"tablist","aria-multiselectable":"true"},[o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingData"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#data","aria-expanded":"false","aria-controls":"data"}," How is my data handled? ")])]),o("div",{id:"data",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingData"},[o("div",{class:"panel-body p-3"},[o("p",null," Data stored on Anonymous GitHub is never shared or used for any purpose beyond providing the anonymization service. When a repository is removed or expires, only its configuration is retained \u2014 this makes it easy to restore the repository if needed and ensures that no future repository reuses the same ID. ")])])]),o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingPermissions"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#permissions","aria-expanded":"false","aria-controls":"permissions"}," Why does GitHub say Anonymous GitHub asks for write access? ")])]),o("div",{id:"permissions",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingPermissions"},[o("div",{class:"panel-body p-3"},[o("p",null,[b("The GitHub App has read-only access to the repositories you choose. You can change repository access on your "),o("a",{href:"/connections"},"GitHub connections"),b(" page.")]),o("p",null,"Legacy GitHub OAuth includes write access to private repositories because GitHub's OAuth scopes do not offer a read-only option for them. Existing OAuth anonymizations and gists can still use this connection."),o("p",null,"Anonymous GitHub reads repository content. It never pushes, modifies, or deletes files on GitHub.")])])]),o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingViewer"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#viewer","aria-expanded":"false","aria-controls":"viewer"}," Can repository owners see who viewed their anonymized repository? ")])]),o("div",{id:"viewer",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingViewer"},[o("div",{class:"panel-body p-3"},[o("p",null," No. Only the total number of views is tracked as an incremental counter. It is not possible for the repository owner or for Anonymous GitHub to identify individual viewers. Reviewer anonymity is fully preserved. ")])])])]),p(" Self-Hosting "),o("h4",{class:"faq-cat"},[o("svg",{class:"ui-icon mr-2","aria-hidden":"true"},[o("use",{href:"#project-icon-storage"})]),b("Self-Hosting ")]),o("div",{class:"panel-group",id:"faq-hosting",role:"tablist","aria-multiselectable":"true"},[o("div",{class:"panel panel-default mb-3"},[o("div",{class:"panel-heading p-3",role:"tab",id:"headingDeploy"},[o("h3",{class:"panel-title"},[o("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-hosting",href:"#deploy","aria-expanded":"false","aria-controls":"deploy"}," Can I deploy my own instance? ")])]),o("div",{id:"deploy",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingDeploy"},[o("div",{class:"panel-body p-3"},[o("p",null,[b(" Yes. Anonymous Git Line truncated
anonymized repository often think it is about the paper, so the
label names the service and the tooltip says who to contact. `),t[12]||(t[12]=o("li",{class:"nav-item d-none d-lg-block"},[o("a",{class:"nav-link",target:"_blank",rel:"noopener",href:"https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml",title:"Report a problem with the Anonymous GitHub service. Questions about an anonymized repository or paper go to its authors.","data-offset":"30"},[o("svg",{class:"ui-icon d-lg-none mr-1","aria-hidden":"true"},[o("use",{href:"#ui-icon-bug"})]),b("Report a bug ")])],-1)),e.isDarkMode?p("v-if",!0):(l(),d("li",WI,[o("a",{class:"nav-link nav-icon",href:"#",title:"Switch to dark mode","aria-label":"Switch to dark mode",role:"button",onClick:t[0]||(t[0]=me(s=>{e.darkMode(!0)},["prevent"]))},[...t[7]||(t[7]=[o("svg",{class:"ui-icon","aria-hidden":"true"},[o("use",{href:"#ui-icon-moon"})],-1)])])])),e.isDarkMode?(l(),d("li",KI,[o("a",{class:"nav-link nav-icon",href:"#",title:"Switch to light mode","aria-label":"Switch to light mode",role:"button",onClick:t[1]||(t[1]=me(s=>{e.darkMode(!1)},["prevent"]))},[...t[8]||(t[8]=[o("svg",{class:"ui-icon","aria-hidden":"true"},[o("use",{href:"#ui-icon-sun"})],-1)])])])):p("v-if",!0),e.user?p("v-if",!0):(l(),d("li",YI,[...t[9]||(t[9]=[o("a",{class:"nav-link btn-signin",target:"_self",href:"/signin","data-offset":"30"},[o("i",{class:"fab fa-github mr-1"}),b(" Sign in ")],-1)])])),e.user?(l(),d("li",JI,[o("a",QI,[e.user?.photo?(l(),d("img",{key:0,width:"22",height:"22",class:"rounded-circle",src:e.safeUrl(e.user?.photo)},null,8,XI)):p("v-if",!0),o("span",ZI,c(e.user?.username),1)]),o("div",xI,[o("h6",e3,"Signed in as @"+c(e.user?.username),1),t[10]||(t[10]=ce('<a class="dropdown-item" href="/profile"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-filter"></use></svg><span>Settings<small>Defaults, quotas, account</small></span></a><a class="dropdown-item" href="/connections"><i class="fab fa-github" aria-hidden="true"></i><span>GitHub connections<small>Repository access and OAuth</small></span></a><a class="dropdown-item" href="/claim"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-link"></use></svg><span>Claim an anonymization<small>Attach an existing mirror to your account</small></span></a><div class="dropdown-divider"></div><a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg><span>Report a bug<small>About this service, not a paper</small></span></a><a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/" target="_blank" rel="noopener"><i class="fab fa-github" aria-hidden="true"></i><span>Source code</span></a><a class="dropdown-item" href="https://ko-fi.com/tdurieux" target="_blank" rel="noopener"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-heart"></use></svg><span>Support the project</span></a><div class="dropdown-divider"></div><a class="dropdown-item" href="/api/user/logout" target="_self"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-logout"></use></svg><span>Sign out</span></a>',9))])])):p("v-if",!0)])])],2),e.generalMessage?(l(),d("div",{key:0,class:"navbar shadow generalMessage",innerHTML:e.sanitize(e.generalMessage)},null,8,t3)):p("v-if",!0)],64)}var o3={class:"paper-landing"},s3={class:"paper-hero"},n3={class:"paper-hero-copy"},r3={class:"paper-cta-row"},i3={key:0,href:"/signin",target:"_self",class:"btn-hero"},a3={key:1,href:"/anonymize",class:"btn-hero"},l3={class:"paper-stats paper-wrap",id:"metrics","aria-label":"Usage over the last 60 days"},d3={class:"paper-stats-grid"},u3={class:"paper-stat-value"},c3={class:"paper-stat-label"},p3=["viewBox"],f3=["x","y","width","height"],m3={key:1,class:"paper-stat-delta"},h3={class:"paper-features paper-wrap","aria-label":"Product features"},v3={class:"paper-features-list",role:"tablist","aria-label":"Features","aria-orientation":"vertical"},g3=["onClick","onKeydown","id","aria-controls","aria-selected","tabindex"],y3={class:"paper-eyebrow"},b3={class:"paper-feature-title"},w3={class:"paper-feature-more"},k3={class:"paper-feature-title paper-feature-title-m","aria-hidden":"true"},E3=["href","target"],C3={class:"paper-features-stage"},_3=["id","aria-labelledby"],S3={class:"paper-frame"},N3={class:"paper-frame-bar","aria-hidden":"true"},D3={class:"url"},R3=["src","alt"],T3={class:"paper-footer"},A3={class:"paper-footer-inner"},O3={class:"paper-footer-col"},I3={key:0,href:"/dashboard"},P3={key:1,href:"/conferences"},V3={class:"paper-footer-foot"};function Nl(e,t){return l(),d("div",o3,[p(" Hero: copy on the left, the product on the right "),o("section",s3,[o("div",n3,[t[2]||(t[2]=o("div",{class:"paper-eyebrow"},"For double-blind peer review",-1)),t[3]||(t[3]=o("h1",{class:"hero-title"},[b(" Share the code,"),o("br"),o("span",{class:"accent"},"not the author.")],-1)),t[4]||(t[4]=o("p",{class:"hero-subtitle Line truncated
label names the service and the tooltip says who to contact. `),t[12]||(t[12]=o("li",{class:"nav-item d-none d-lg-block"},[o("a",{class:"nav-link",target:"_blank",rel:"noopener",href:"https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml",title:"Report a problem with the Anonymous GitHub service. Questions about an anonymized repository or paper go to its authors.","data-offset":"30"},[o("svg",{class:"ui-icon d-lg-none mr-1","aria-hidden":"true"},[o("use",{href:"#ui-icon-bug"})]),b("Report a bug ")])],-1)),e.isDarkMode?p("v-if",!0):(l(),d("li",Y4,[o("a",{class:"nav-link nav-icon",href:"#",title:"Switch to dark mode","aria-label":"Switch to dark mode",role:"button",onClick:t[0]||(t[0]=me(n=>{e.darkMode(!0)},["prevent"]))},[...t[7]||(t[7]=[o("svg",{class:"ui-icon","aria-hidden":"true"},[o("use",{href:"#ui-icon-moon"})],-1)])])])),e.isDarkMode?(l(),d("li",J4,[o("a",{class:"nav-link nav-icon",href:"#",title:"Switch to light mode","aria-label":"Switch to light mode",role:"button",onClick:t[1]||(t[1]=me(n=>{e.darkMode(!1)},["prevent"]))},[...t[8]||(t[8]=[o("svg",{class:"ui-icon","aria-hidden":"true"},[o("use",{href:"#ui-icon-sun"})],-1)])])])):p("v-if",!0),e.user?p("v-if",!0):(l(),d("li",Q4,[...t[9]||(t[9]=[o("a",{class:"nav-link btn-signin",target:"_self",href:"/signin","data-offset":"30"},[o("i",{class:"fab fa-github mr-1"}),b(" Sign in ")],-1)])])),e.user?(l(),d("li",X4,[o("a",Z4,[e.user?.photo?(l(),d("img",{key:0,width:"22",height:"22",class:"rounded-circle",src:e.safeUrl(e.user?.photo)},null,8,x4)):p("v-if",!0),o("span",e3,c(e.user?.username),1)]),o("div",t3,[o("h6",o3,"Signed in as @"+c(e.user?.username),1),t[10]||(t[10]=ce('<a class="dropdown-item" href="/profile"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-filter"></use></svg><span>Settings<small>Defaults, quotas, account</small></span></a><a class="dropdown-item" href="/connections"><i class="fab fa-github" aria-hidden="true"></i><span>GitHub connections<small>Repository access and OAuth</small></span></a><a class="dropdown-item" href="/claim"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-link"></use></svg><span>Claim an anonymization<small>Attach an existing mirror to your account</small></span></a><div class="dropdown-divider"></div><a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg><span>Report a bug<small>About this service, not a paper</small></span></a><a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/" target="_blank" rel="noopener"><i class="fab fa-github" aria-hidden="true"></i><span>Source code</span></a><a class="dropdown-item" href="https://ko-fi.com/tdurieux" target="_blank" rel="noopener"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-heart"></use></svg><span>Support the project</span></a><div class="dropdown-divider"></div><a class="dropdown-item" href="/api/user/logout" target="_self"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-logout"></use></svg><span>Sign out</span></a>',9))])])):p("v-if",!0)])])],2),e.generalMessage?(l(),d("div",{key:0,class:"navbar shadow generalMessage",innerHTML:e.sanitize(e.generalMessage)},null,8,s3)):p("v-if",!0)],64)}var n3={class:"paper-landing"},r3={class:"paper-hero"},i3={class:"paper-hero-copy"},a3={class:"paper-cta-row"},l3={key:0,href:"/signin",target:"_self",class:"btn-hero"},d3={key:1,href:"/anonymize",class:"btn-hero"},u3={class:"paper-stats paper-wrap",id:"metrics","aria-label":"Usage over the last 60 days"},c3={class:"paper-stats-grid"},p3={class:"paper-stat-value"},f3={class:"paper-stat-label"},m3=["viewBox"],h3=["x","y","width","height"],v3={key:1,class:"paper-stat-delta"},g3={class:"paper-features paper-wrap","aria-label":"Product features"},y3={class:"paper-features-list",role:"tablist","aria-label":"Features","aria-orientation":"vertical"},b3=["onClick","onKeydown","id","aria-controls","aria-selected","tabindex"],w3={class:"paper-eyebrow"},k3={class:"paper-feature-title"},E3={class:"paper-feature-more"},C3={class:"paper-feature-title paper-feature-title-m","aria-hidden":"true"},_3=["href","target"],S3={class:"paper-features-stage"},N3=["id","aria-labelledby"],D3={class:"paper-frame"},R3={class:"paper-frame-bar","aria-hidden":"true"},T3={class:"url"},A3=["src","alt"],O3={class:"paper-footer"},I3={class:"paper-footer-inner"},P3={class:"paper-footer-col"},V3={key:0,href:"/dashboard"},q3={key:1,href:"/conferences"},M3={class:"paper-footer-foot"};function Dl(e,t){return l(),d("div",n3,[p(" Hero: copy on the left, the product on the right "),o("section",r3,[o("div",i3,[t[2]||(t[2]=o("div",{class:"paper-eyebrow"},"For double-blind peer review",-1)),t[3]||(t[3]=o("h1",{class:"hero-title"},[b(" Share the code,"),o("br"),o("span",{class:"accent"},"not the author.")],-1)),t[4]||(t[4]=o("p",{class:"hero-subtitle Line truncated
MIT CSAIL
jane.smith@example.org`},_5={id:"termsHelp",class:"form-text text-muted"},S5={class:"invalid-feedback"},N5={id:"settings-display",class:"paper-settings-section"},D5={class:"form-group paper-check-list"},R5={class:"form-check"},T5={class:"form-check-input",type:"checkbox",id:"link",name:"link"},A5={class:"form-check"},O5={class:"form-check-input",type:"checkbox",id:"image",name:"image"},I5={class:"form-check"},P5={class:"form-check-input",type:"checkbox",id:"pdf",name:"pdf"},V5={class:"form-check"},q5={class:"form-check-input",type:"checkbox",id:"notebook",name:"notebook"},M5={id:"settings-features",class:"paper-settings-section"},$5={class:"form-group paper-check-list"},L5={class:"form-check"},U5={class:"form-check-input",type:"checkbox",id:"page",name:"page"},F5={class:"form-check"},z5={class:"form-check-input",type:"checkbox",id:"loc",name:"loc"},H5={class:"form-check"},B5={class:"form-check-input",type:"checkbox",id:"update",name:"update"},j5={class:"form-group"},G5={class:"form-control",id:"mode",name:"mode"},W5={class:"form-text text-muted"},K5={id:"settings-expiration",class:"paper-settings-section"},Y5={class:"form-group"},J5={class:"form-control",id:"expiration",name:"expiration"},Q5={key:0,class:"draft-status",role:"status"},X5={key:1,class:"draft-status"},Z5={class:"paper-settings-footer"},x5={key:0,class:"paper-inline-alert paper-inline-alert-error",role:"alert"},eq=["textContent"],tq={class:"paper-settings-actions"},oq=["disabled"],sq={key:0,class:"ui-icon mr-1","aria-hidden":"true"},nq={id:"settings-account",class:"paper-settings-section"},rq={class:"paper-account-card"},iq=["src"],aq={class:"paper-account-identity"},lq={class:"paper-account-name"},dq={class:"paper-danger-zone"},uq={key:0,class:"paper-inline-alert paper-inline-alert-error",role:"alert"},cq=["textContent"],pq=["disabled"];function Ol(e,t){let s=We("quota-usage"),r=be("paper-scrollspy"),n=be("field"),i=be("form");return l(),d("div",m5,[t[52]||(t[52]=ce('<div class="paper-crumbs"><a href="/dashboard">My work</a> \xA0/\xA0 <span class="here">Settings</span></div><h1 class="paper-page-title">Your <em>settings</em></h1><p class="paper-page-lede">Defaults applied to every new anonymization, your quotas, and your account.</p>',3)),o("div",h5,[N((l(),d("aside",v5,[...t[4]||(t[4]=[ce('<div class="paper-settings-toc-head">On this page</div><nav><a href="#settings-quota">Quota</a><a href="#settings-terms">Terms to redact</a><a href="#settings-display">Display</a><a href="#settings-features">Features</a><a href="#settings-expiration">Expiration</a><a href="#settings-account">Account</a></nav>',2)])])),[[r]]),o("div",g5,[p(" Account usage shares the dashboard component. "),o("section",y5,[t[6]||(t[6]=o("div",{class:"paper-section-eyebrow"},"Quota",-1)),t[7]||(t[7]=o("p",{class:"paper-section-copy"},"What you are using across all your anonymizations. Conferences can lift these limits during a review window.",-1)),e.quota?(l(),Mt(s,{key:0,quota:e.quota,fmt:e.fmt},null,8,["quota","fmt"])):p("v-if",!0),e.quota?p("v-if",!0):(l(),d("div",b5,[(l(),d(X,null,re([1,2,3],(a,u)=>o("div",w5,[...t[5]||(t[5]=[o("div",{class:"quota-header"},[o("span",{class:"skeleton skeleton-line",style:{width:"40%"}}),o("span",{class:"skeleton skeleton-line",style:{width:"25%"}})],-1),o("div",{class:"quota-track"},null,-1)])])),64))]))]),N((l(),d("form",{class:"form needs-validation",name:"defaultsForm",novalidate:"",onSubmit:t[2]||(t[2]=me(a=>e.submitForm(a,()=>{e.saveDefault(a)}),["prevent"]))},[o("section",k5,[t[13]||(t[13]=o("div",{class:"paper-section-eyebrow"},"Terms to redact",-1)),t[14]||(t[14]=o("p",{class:"paper-section-copy"},"Pre-filled in every new anonymization. You can still edit the list per anonymization.",-1)),o("div",E5,[t[12]||(t[12]=o("label",{class:"paper-field-label",for:"terms"},"Default terms",-1)),N(o("textarea",C5,null,512),[[n,{state:e.viewState,set:a=>{e.terms=a},value:e.terms,form:"defaultsForm",options:{debounce:250}}]]),o("small",_5,[t[8]||(t[8]=b(" One term per line (regex allowed). Each match is replaced by ",-1)),o("code",null,c(e.site_options?.ANONYMIZATION_MASK||"XXX")+"-[N]",1),t[9]||(t[9]=b(", or use ",-1)),t[10]||(t[10]=o("code",null,"term=>replacement",-1)),t[11]||(t[11]=b(" to pick your own. ",-1))]),N(o("div",S5," Terms are in an invalid format ",512),[[Y,e.defaultsForm?.terms?.errors?.format]])])]),o("section",N5,[t[23]||(t[23]=o("div",{class:"paper-section-eyebrow"},"Display",-1)),t[24]||(t[24]=o("p",{class:"paper-section-copy"},"What reviewers can see inside an anonymized repository.",-1)),o("div",D5,[o("div",R5,[N(o("input",T5,null,512),[[n,{state:e.viewState,set:a=>{e.options.link=a},value:e.options?.link,form:"defaultsForm",options:{}}]]),t[15]||(t[15]=o("label",{class:"form-check-label",for:"link"},"Keep links",-1)),t[16]||(t[16]=o("small",{class:"form-text text-muted"},"Unchecked, every link in text files is removed.",-1))]),o("div",A5,[N(o("input",O5,null,512),[[n,{state:e.viewState,set:a=>{e.options.image=a},value:e.op Line truncated
`);for(let k=0;k<v.length;k++){let m=v[k];if(m.startsWith("diff --git")){h(),f();continue}if(m.startsWith("--- ")){i&&i.lines.length&&h(),f().oldPath=m.replace(/^--- (a\/)?/,"").trim();continue}if(m.startsWith("+++ ")){f().newPath=m.replace(/^\+\+\+ (b\/)?/,"").trim();continue}if(!(m.startsWith("index ")||m.startsWith("similarity index")||m.startsWith("rename ")||m.startsWith("new file mode")||m.startsWith("deleted file mode")||m.startsWith("Binary files"))){if(m.startsWith("@@")){let y=m.match(/@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);y&&(a=parseInt(y[1],10),u=parseInt(y[2],10)),f().lines.push({kind:"hunk",oldNo:"",newNo:"",text:m});continue}i&&(m.startsWith("+")?(i.lines.push({kind:"add",oldNo:"",newNo:u,text:m.slice(1)}),u++):m.startsWith("-")?(i.lines.push({kind:"remove",oldNo:a,newNo:"",text:m.slice(1)}),a++):(i.lines.push({kind:"ctx",oldNo:a,newNo:u,text:m.startsWith(" ")?m.slice(1):m}),a++,u++))}}return s(n,i),n.join("")}})({trustAsHtml:e=>e});function DM(e,t){return e==null?"":Number(e).toLocaleString(void 0,t==null?{}:{minimumFractionDigits:t,maximumFractionDigits:t})}function RM(e){return String(e??"").toUpperCase()}function TM(e,t,s=0){return(e||[]).slice(s,s+t)}function AM(e,t){return Array.isArray(e)?typeof t=="function"?e.filter(t):e:[]}function OM(e,t){if(!t)return e||[];let s=Array.isArray(t)?t:[t];return[...e||[]].sort((r,n)=>{for(let i of s){let a=i[0]==="-"?-1:1;i=i.replace(/^[+-]/,"");let u=i.split(".").reduce((h,v)=>h?.[v],r),f=i.split(".").reduce((h,v)=>h?.[v],n);if(typeof u=="string"&&(u=u.toLowerCase()),typeof f=="string"&&(f=f.toLowerCase()),u!==f)return(u==null?-1:f==null?1:u<f?-1:1)*a}return 0})}function IM(e,t){if(!e)return"";let s=new Date(e);if(isNaN(s))return String(e);if(!t||t==="mediumDate")return s.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"});let r={yyyy:s.getFullYear(),MMM:s.toLocaleDateString(void 0,{month:"short"}),MM:String(s.getMonth()+1).padStart(2,"0"),dd:String(s.getDate()).padStart(2,"0"),d:s.getDate(),HH:String(s.getHours()).padStart(2,"0"),mm:String(s.getMinutes()).padStart(2,"0"),ss:String(s.getSeconds()).padStart(2,"0")};return t.replace(/yyyy|MMM|MM|dd|HH|mm|ss|d/g,n=>r[n])}function PM(e,t){return(t?.[e]??t?.[e===1?"one":"other"]??"").replace(/\{\}/g,e??0)}var Ul={ERRORS:{cache_busy:"Downloads are busy. Please try again shortly.",invalid_sort:"The selected sort order is invalid.",invalid_cursor:"This project page is no longer valid. Reload the list.",repository_changed:"The repository changed while processing. Please retry.",repository_job_cancelled:"The repository changed or was removed while processing.",storage_delete_failed:"Unable to remove cached files. Please try again later.",unknown_error:"Unknown error, contact the admin.",unreachable:"Anonymous GitHub is unreachable, contact the admin.",request_error:"Unable to download the file, check your connection or contact the admin.",not_found:"The requested resource could not be found.",not_connected:"You must be logged in to perform this action.",not_authorized:"You do not have permission to perform this action.",unable_to_connect_user:"Unable to connect your account. Please try again later.",user_not_found:"The requested user could not be found.",user_banned:"Your account has been banned. Contact the admin for more information.",repo_access_limited:"GitHub blocked access because the repository's organization restricts third-party OAuth apps. Ask an org owner to approve Anonymous GitHub under Settings \u2192 Third-party Access \u2192 OAuth app policy, or anonymize a personal fork instead.",repo_saml_enforcement:"The repository's organization enforces SAML single sign-on. Authorize your token for that organization (GitHub \u2192 Settings \u2192 Applications, or re-run the org's SSO sign-in), then retry. Alternatively, anonymize a personal fork.",repo_not_found:"The repository was not found on GitHub. Check the URL and spelling, make sure you are signed in to the account that can see it, and confirm the repo isn't hidden under an org that restricts third-party app access.",repo_empty:"The selected branch has no commits on GitHub. Push at least one commit, or pick a different branch, then retry.",repo_not_accessible:"Anonymous GitHub cannot access this repository. Verify the repository exists and that Anonymous GitHub has been authorized for the owning organization.",repository_archived:"This repository has been archived and its files are no longer available.",repository_expired:"The repository is expired.",invalid_status:"This action cannot be performed while the resource is in its current state.",repository_not_ready:"Anonymous GitHub is still processing the repository, it can take several minutes.",repository_not_accessible:"This repository is currently not accessible.",repo_is_updating:"Anonymous GitHub is still processing the repository, it can take several minutes.",invalid_repo:"The provided repository is not valid.",invalid_source_repository:"The repository source name Line truncated
jane.smith@example.org`},N5={id:"termsHelp",class:"form-text text-muted"},D5={class:"invalid-feedback"},R5={id:"settings-display",class:"paper-settings-section"},T5={class:"form-group paper-check-list"},A5={class:"form-check"},O5={class:"form-check-input",type:"checkbox",id:"link",name:"link"},I5={class:"form-check"},P5={class:"form-check-input",type:"checkbox",id:"image",name:"image"},V5={class:"form-check"},q5={class:"form-check-input",type:"checkbox",id:"pdf",name:"pdf"},M5={class:"form-check"},L5={class:"form-check-input",type:"checkbox",id:"notebook",name:"notebook"},$5={id:"settings-features",class:"paper-settings-section"},U5={class:"form-group paper-check-list"},F5={class:"form-check"},z5={class:"form-check-input",type:"checkbox",id:"page",name:"page"},H5={class:"form-check"},B5={class:"form-check-input",type:"checkbox",id:"loc",name:"loc"},j5={class:"form-check"},G5={class:"form-check-input",type:"checkbox",id:"update",name:"update"},W5={class:"form-group"},K5={class:"form-control",id:"mode",name:"mode"},Y5={class:"form-text text-muted"},J5={id:"settings-expiration",class:"paper-settings-section"},Q5={class:"form-group"},X5={class:"form-control",id:"expiration",name:"expiration"},Z5={key:0,class:"draft-status",role:"status"},x5={key:1,class:"draft-status"},eq={class:"paper-settings-footer"},tq={key:0,class:"paper-inline-alert paper-inline-alert-error",role:"alert"},oq=["textContent"],sq={class:"paper-settings-actions"},nq=["disabled"],rq={key:0,class:"ui-icon mr-1","aria-hidden":"true"},iq={id:"settings-account",class:"paper-settings-section"},aq={class:"paper-account-card"},lq=["src"],dq={class:"paper-account-identity"},uq={class:"paper-account-name"},cq={class:"paper-danger-zone"},pq={key:0,class:"paper-inline-alert paper-inline-alert-error",role:"alert"},fq=["textContent"],mq=["disabled"];function Il(e,t){let n=We("quota-usage"),r=be("paper-scrollspy"),s=be("field"),i=be("form");return l(),d("div",v5,[t[52]||(t[52]=ce('<div class="paper-crumbs"><a href="/dashboard">My work</a> \xA0/\xA0 <span class="here">Settings</span></div><h1 class="paper-page-title">Your <em>settings</em></h1><p class="paper-page-lede">Defaults applied to every new anonymization, your quotas, and your account.</p>',3)),o("div",g5,[N((l(),d("aside",y5,[...t[4]||(t[4]=[ce('<div class="paper-settings-toc-head">On this page</div><nav><a href="#settings-quota">Quota</a><a href="#settings-terms">Terms to redact</a><a href="#settings-display">Display</a><a href="#settings-features">Features</a><a href="#settings-expiration">Expiration</a><a href="#settings-account">Account</a></nav>',2)])])),[[r]]),o("div",b5,[p(" Account usage shares the dashboard component. "),o("section",w5,[t[6]||(t[6]=o("div",{class:"paper-section-eyebrow"},"Quota",-1)),t[7]||(t[7]=o("p",{class:"paper-section-copy"},"What you are using across all your anonymizations. Conferences can lift these limits during a review window.",-1)),e.quota?(l(),Lt(n,{key:0,quota:e.quota,fmt:e.fmt},null,8,["quota","fmt"])):p("v-if",!0),e.quota?p("v-if",!0):(l(),d("div",k5,[(l(),d(X,null,re([1,2,3],(a,u)=>o("div",E5,[...t[5]||(t[5]=[o("div",{class:"quota-header"},[o("span",{class:"skeleton skeleton-line",style:{width:"40%"}}),o("span",{class:"skeleton skeleton-line",style:{width:"25%"}})],-1),o("div",{class:"quota-track"},null,-1)])])),64))]))]),N((l(),d("form",{class:"form needs-validation",name:"defaultsForm",novalidate:"",onSubmit:t[2]||(t[2]=me(a=>e.submitForm(a,()=>{e.saveDefault(a)}),["prevent"]))},[o("section",C5,[t[13]||(t[13]=o("div",{class:"paper-section-eyebrow"},"Terms to redact",-1)),t[14]||(t[14]=o("p",{class:"paper-section-copy"},"Pre-filled in every new anonymization. You can still edit the list per anonymization.",-1)),o("div",_5,[t[12]||(t[12]=o("label",{class:"paper-field-label",for:"terms"},"Default terms",-1)),N(o("textarea",S5,null,512),[[s,{state:e.viewState,set:a=>{e.terms=a},value:e.terms,form:"defaultsForm",options:{debounce:250}}]]),o("small",N5,[t[8]||(t[8]=b(" One term per line (regex allowed). Each match is replaced by ",-1)),o("code",null,c(e.site_options?.ANONYMIZATION_MASK||"XXX")+"-[N]",1),t[9]||(t[9]=b(", or use ",-1)),t[10]||(t[10]=o("code",null,"term=>replacement",-1)),t[11]||(t[11]=b(" to pick your own. ",-1))]),N(o("div",D5," Terms are in an invalid format ",512),[[Y,e.defaultsForm?.terms?.errors?.format]])])]),o("section",R5,[t[23]||(t[23]=o("div",{class:"paper-section-eyebrow"},"Display",-1)),t[24]||(t[24]=o("p",{class:"paper-section-copy"},"What reviewers can see inside an anonymized repository.",-1)),o("div",T5,[o("div",A5,[N(o("input",O5,null,512),[[s,{state:e.viewState,set:a=>{e.options.link=a},value:e.options?.link,form:"defaultsForm",options:{}}]]),t[15]||(t[15]=o("label",{class:"form-check-label",for:"link"},"Keep links",-1)),t[16]||(t[16]=o("small",{class:"form-text text-muted"},"Unchecked, every link in text files is removed.",-1))]),o("div",I5,[N(o("input",P5,null,512),[[s,{state:e.viewState,set:a=>{e.options.image=a},value:e.op Line truncated
`);for(let k=0;k<g.length;k++){let m=g[k];if(m.startsWith("diff --git")){h(),f();continue}if(m.startsWith("--- ")){i&&i.lines.length&&h(),f().oldPath=m.replace(/^--- (a\/)?/,"").trim();continue}if(m.startsWith("+++ ")){f().newPath=m.replace(/^\+\+\+ (b\/)?/,"").trim();continue}if(!(m.startsWith("index ")||m.startsWith("similarity index")||m.startsWith("rename ")||m.startsWith("new file mode")||m.startsWith("deleted file mode")||m.startsWith("Binary files"))){if(m.startsWith("@@")){let y=m.match(/@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);y&&(a=parseInt(y[1],10),u=parseInt(y[2],10)),f().lines.push({kind:"hunk",oldNo:"",newNo:"",text:m});continue}i&&(m.startsWith("+")?(i.lines.push({kind:"add",oldNo:"",newNo:u,text:m.slice(1)}),u++):m.startsWith("-")?(i.lines.push({kind:"remove",oldNo:a,newNo:"",text:m.slice(1)}),a++):(i.lines.push({kind:"ctx",oldNo:a,newNo:u,text:m.startsWith(" ")?m.slice(1):m}),a++,u++))}}return n(s,i),s.join("")}})({trustAsHtml:e=>e});function TM(e,t){return e==null?"":Number(e).toLocaleString(void 0,t==null?{}:{minimumFractionDigits:t,maximumFractionDigits:t})}function AM(e){return String(e??"").toUpperCase()}function OM(e,t,n=0){return(e||[]).slice(n,n+t)}function IM(e,t){return Array.isArray(e)?typeof t=="function"?e.filter(t):e:[]}function PM(e,t){if(!t)return e||[];let n=Array.isArray(t)?t:[t];return[...e||[]].sort((r,s)=>{for(let i of n){let a=i[0]==="-"?-1:1;i=i.replace(/^[+-]/,"");let u=i.split(".").reduce((h,g)=>h?.[g],r),f=i.split(".").reduce((h,g)=>h?.[g],s);if(typeof u=="string"&&(u=u.toLowerCase()),typeof f=="string"&&(f=f.toLowerCase()),u!==f)return(u==null?-1:f==null?1:u<f?-1:1)*a}return 0})}function VM(e,t){if(!e)return"";let n=new Date(e);if(isNaN(n))return String(e);if(!t||t==="mediumDate")return n.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"});let r={yyyy:n.getFullYear(),MMM:n.toLocaleDateString(void 0,{month:"short"}),MM:String(n.getMonth()+1).padStart(2,"0"),dd:String(n.getDate()).padStart(2,"0"),d:n.getDate(),HH:String(n.getHours()).padStart(2,"0"),mm:String(n.getMinutes()).padStart(2,"0"),ss:String(n.getSeconds()).padStart(2,"0")};return t.replace(/yyyy|MMM|MM|dd|HH|mm|ss|d/g,s=>r[s])}function qM(e,t){return(t?.[e]??t?.[e===1?"one":"other"]??"").replace(/\{\}/g,e??0)}var Fl={ERRORS:{cache_busy:"Downloads are busy. Please try again shortly.",invalid_sort:"The selected sort order is invalid.",invalid_cursor:"This project page is no longer valid. Reload the list.",repository_changed:"The repository changed while processing. Please retry.",repository_job_cancelled:"The repository changed or was removed while processing.",storage_delete_failed:"Unable to remove cached files. Please try again later.",unknown_error:"Unknown error, contact the admin.",unreachable:"Anonymous GitHub is unreachable, contact the admin.",request_error:"Unable to download the file, check your connection or contact the admin.",not_found:"The requested resource could not be found.",not_connected:"You must be logged in to perform this action.",not_authorized:"You do not have permission to perform this action.",unable_to_connect_user:"Unable to connect your account. Please try again later.",user_not_found:"The requested user could not be found.",user_banned:"Your account has been banned. Contact the admin for more information.",repo_access_limited:"GitHub blocked access because the repository's organization restricts third-party OAuth apps. Ask an org owner to approve Anonymous GitHub under Settings \u2192 Third-party Access \u2192 OAuth app policy, or anonymize a personal fork instead.",repo_saml_enforcement:"The repository's organization enforces SAML single sign-on. Authorize your token for that organization (GitHub \u2192 Settings \u2192 Applications, or re-run the org's SSO sign-in), then retry. Alternatively, anonymize a personal fork.",repo_not_found:"The repository was not found on GitHub. Check the URL and spelling, make sure you are signed in to the account that can see it, and confirm the repo isn't hidden under an org that restricts third-party app access.",repo_empty:"The selected branch has no commits on GitHub. Push at least one commit, or pick a different branch, then retry.",repo_not_accessible:"Anonymous GitHub cannot access this repository. Verify the repository exists and that Anonymous GitHub has been authorized for the owning organization.",repository_archived:"This repository has been archived and its files are no longer available.",repository_expired:"The repository is expired.",invalid_status:"This action cannot be performed while the resource is in its current state.",repository_not_ready:"Anonymous GitHub is still processing the repository, it can take several minutes.",repository_not_accessible:"This repository is currently not accessible.",repo_is_updating:"Anonymous GitHub is still processing the repository, it can take several minutes.",invalid_repo:"The provided repository is not valid.",invalid_source_repository:"The repository source name Line truncated
/*! Bundled license information:
@vue/shared/dist/shared.esm-bundler.js:
(**
* @vue/shared v3.5.42
* @vue/shared v3.5.43
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**)
@vue/reactivity/dist/reactivity.esm-bundler.js:
(**
* @vue/reactivity v3.5.42
* @vue/reactivity v3.5.43
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**)
@vue/runtime-core/dist/runtime-core.esm-bundler.js:
(**
* @vue/runtime-core v3.5.42
* @vue/runtime-core v3.5.43
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**)
@vue/runtime-dom/dist/runtime-dom.esm-bundler.js:
(**
* @vue/runtime-dom v3.5.42
* @vue/runtime-dom v3.5.43
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**)
vue/dist/vue.runtime.esm-bundler.js:
(**
* vue v3.5.42
* vue v3.5.43
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**)
+44 -42
View File
@@ -31,7 +31,7 @@ const UPSTREAM = process.env.UPSTREAM || "https://anonymous.4open.science";
const PORT = parseInt(process.env.PORT || "4001", 10);
const PUBLIC_DIR = path.resolve(__dirname, "..", "public");
// Re-read manifest on each request so gulp rebuilds are picked up instantly.
// Re-read manifest on each request so asset rebuilds are picked up instantly.
const manifestPath = path.join(PUBLIC_DIR, "asset-manifest.json");
function asset(name) {
try {
@@ -132,49 +132,51 @@ app.use(
selfHandleResponse: true, // so we can rewrite Set-Cookie + HTML
cookieDomainRewrite: "",
cookiePathRewrite: "/",
onProxyReq(proxyReq, req) {
// Make upstream think the request came in over HTTPS at its domain.
proxyReq.setHeader("origin", UPSTREAM);
proxyReq.setHeader("referer", UPSTREAM + req.originalUrl);
},
onProxyRes: responseInterceptor(async (buffer, proxyRes, req, res) => {
// Rewrite Set-Cookie so cookies stick on localhost.
const setCookie = proxyRes.headers["set-cookie"];
if (setCookie) {
const rewritten = setCookie.map((c) =>
c
.replace(/;\s*Secure/gi, "")
.replace(/;\s*Domain=[^;]+/gi, "")
.replace(/;\s*SameSite=None/gi, "; SameSite=Lax"),
);
res.setHeader("set-cookie", rewritten);
}
// Rewrite Location headers on 3xx redirects.
const location = proxyRes.headers["location"];
if (location && typeof location === "string") {
try {
const u = new URL(location, UPSTREAM);
if (u.origin === UPSTREAM) {
res.setHeader("location", u.pathname + u.search + u.hash);
}
} catch {
/* leave as-is */
on: {
proxyReq(proxyReq, req) {
// Make upstream think the request came in over HTTPS at its domain.
proxyReq.setHeader("origin", UPSTREAM);
proxyReq.setHeader("referer", UPSTREAM + req.originalUrl);
},
proxyRes: responseInterceptor(async (buffer, proxyRes, req, res) => {
// Rewrite Set-Cookie so cookies stick on localhost.
const setCookie = proxyRes.headers["set-cookie"];
if (setCookie) {
const rewritten = setCookie.map((c) =>
c
.replace(/;\s*Secure/gi, "")
.replace(/;\s*Domain=[^;]+/gi, "")
.replace(/;\s*SameSite=None/gi, "; SameSite=Lax"),
);
res.setHeader("set-cookie", rewritten);
}
}
const ct = String(proxyRes.headers["content-type"] || "");
if (ct.includes("text/html")) {
// Swap upstream domain references in HTML so relative navigation
// stays on localhost.
const body = buffer
.toString("utf8")
.replace(new RegExp(UPSTREAM.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "");
return body;
}
return buffer;
}),
logLevel: "warn",
// Rewrite Location headers on 3xx redirects.
const location = proxyRes.headers["location"];
if (location && typeof location === "string") {
try {
const u = new URL(location, UPSTREAM);
if (u.origin === UPSTREAM) {
res.setHeader("location", u.pathname + u.search + u.hash);
}
} catch {
/* leave as-is */
}
}
const ct = String(proxyRes.headers["content-type"] || "");
if (ct.includes("text/html")) {
// Swap upstream domain references in HTML so relative navigation
// stays on localhost.
const body = buffer
.toString("utf8")
.replace(new RegExp(UPSTREAM.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "");
return body;
}
return buffer;
}),
},
logger: { info() {}, warn: console.warn, error: console.error },
}),
);
+2 -2
View File
@@ -155,12 +155,12 @@ function ensureRedisGateClient(): Promise<RedisClientType | null> {
socket: {
host: config.REDIS_HOSTNAME,
port: config.REDIS_PORT,
reconnectStrategy: () => false as any,
reconnectStrategy: false,
},
}) as RedisClientType;
c.on("error", () => {
redisGateDisabled = true;
c.disconnect().catch(() => {});
if (c.isOpen) c.destroy();
redisGateReady = null;
});
await c.connect();
+3 -3
View File
@@ -13,7 +13,7 @@ export async function cacheCommand<T>(command: Promise<T>, client: ReturnType<ty
timer = setTimeout(() => {
retryAt = Date.now() + 30_000;
if (connection === client) connection = undefined;
if (client.isOpen) void client.disconnect().catch(() => {});
if (client.isOpen) client.destroy();
reject(new Error("cache_command_timeout"));
}, timeoutMs);
})]);
@@ -30,7 +30,7 @@ export async function cacheRedis() {
} });
client.on("error", () => {});
try { await cacheCommand(client.connect(), client, 500); connection = client; return client; }
catch { retryAt = Date.now() + 30_000; if (client.isOpen) await client.disconnect().catch(() => {}); return undefined; }
catch { retryAt = Date.now() + 30_000; if (client.isOpen) client.destroy(); return undefined; }
finally { connecting = undefined; }
})();
return connecting;
@@ -79,5 +79,5 @@ export async function closeCacheRedis() {
const current = connection;
connection = undefined;
retryAt = 0;
if (current?.isOpen) await current.disconnect();
if (current?.isOpen) current.destroy();
}
+1 -1
View File
@@ -74,7 +74,7 @@ function getRedis(): RedisClientType | null {
const c = redisClient;
redisClient = null;
if (c?.isOpen) {
c.disconnect().catch(() => {});
c.destroy();
}
});
redisClient.connect().catch(() => {
+6 -3
View File
@@ -30,7 +30,10 @@ async function redisDeadline<T>(work: Promise<T>, redis: RedisClientType): Promi
return await Promise.race([work, new Promise<never>((_resolve, reject) => {
timer = setTimeout(() => {
reject(new Error("performance_monitoring_timeout"));
if (redis.isOpen) void redis.disconnect().then(() => client === redis ? redis.connect() : undefined).catch(() => {});
if (redis.isOpen) {
redis.destroy();
if (client === redis) void redis.connect().catch(() => {});
}
}, 2000); timer.unref();
})]);
} finally { clearTimeout(timer); }
@@ -75,7 +78,7 @@ export async function socketSnapshot() {
export function startPerformanceMonitoring(service: Service, workers: () => unknown = () => null) {
if (stop) return stop;
const redis = createClient({ disableOfflineQueue: true, socket: { host: config.REDIS_HOSTNAME,
port: config.REDIS_PORT, connectTimeout: 2000, reconnectStrategy: retries => Math.min(5000, 250 * (retries + 1)) } }) as RedisClientType;
port: config.REDIS_PORT, connectTimeout: 2000, reconnectStrategy: (retries: number) => Math.min(5000, 250 * (retries + 1)) } }) as RedisClientType;
client = redis;
redis.on("error", () => {});
void redis.connect().catch(() => {});
@@ -135,7 +138,7 @@ export function startPerformanceMonitoring(service: Service, workers: () => unkn
void flush();
stop = async () => {
stopped = true; clearInterval(timer); loop.disable();
if (redis.isOpen) await redis.disconnect();
if (redis.isOpen) redis.destroy();
client = undefined; stop = undefined; flushNow = undefined; reports.clear();
};
return stop;
+2 -2
View File
@@ -15,12 +15,12 @@ function getClient(): RedisClientType | null {
socket: {
host: config.REDIS_HOSTNAME,
port: config.REDIS_PORT,
reconnectStrategy: () => false as any,
reconnectStrategy: false,
},
}) as RedisClientType;
client.on("error", () => {
disabled = true;
client?.disconnect().catch(() => {});
if (client?.isOpen) client.destroy();
client = null;
});
client.connect().catch(() => {
+19 -17
View File
@@ -11,26 +11,28 @@ async function main() {
await client.connect();
let found = 0;
let removed = 0;
for await (const key of client.scanIterator({ MATCH: "anoGH_session:*", COUNT: 100 })) {
const raw = await client.get(key);
if (!raw) continue;
let legacy = false;
try {
const value = JSON.parse(raw);
legacy = !!value.passport?.user && typeof value.passport.user !== "string";
} catch { legacy = true; }
if (!legacy) continue;
found++;
if (args.includes("--apply")) {
// Do not remove a session replaced since the scan read it.
removed += Number(await client.eval(
'if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end',
{ keys: [key], arguments: [raw] }
));
for await (const keys of client.scanIterator({ MATCH: "anoGH_session:*", COUNT: 100 })) {
for (const key of keys) {
const raw = await client.get(key);
if (!raw) continue;
let legacy = false;
try {
const value = JSON.parse(raw);
legacy = !!value.passport?.user && typeof value.passport.user !== "string";
} catch { legacy = true; }
if (!legacy) continue;
found++;
if (args.includes("--apply")) {
// Do not remove a session replaced since the scan read it.
removed += Number(await client.eval(
'if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end',
{ keys: [key], arguments: [raw] }
));
}
}
}
process.stdout.write(JSON.stringify({ found, removed }) + "\n");
} finally { if (client.isOpen) await client.quit(); }
} finally { if (client.isOpen) await client.close(); }
}
main().catch(() => {
process.stderr.write("Legacy session cleanup failed; check Redis configuration and connectivity.\n");
+7 -37
View File
@@ -34,7 +34,8 @@ import {
} from "./dailyStatsSnapshot";
import { getUser } from "./routes/route-utils";
import config from "../config";
import { resolveTrustProxy, isCloudflareIP } from "./trustProxy";
import { resolveTrustProxy } from "./trustProxy";
import { requestRateLimitKey } from "./rate-limit-key";
import { createLogger, serializeError } from "../core/logger";
import { monitorRequests } from "../core/request-monitoring";
import { startPerformanceMonitoring } from "../core/performance-monitoring";
@@ -156,37 +157,6 @@ export default async function start() {
await redisClient.connect();
function keyGenerator(
request: express.Request,
_response: express.Response
): string {
// Use request.ip, which Express resolves from X-Forwarded-For honouring
// the configured "trust proxy" setting. Do NOT key off the
// cf-connecting-ip header unconditionally: when the server isn't actually
// behind Cloudflare a client can set that header to an arbitrary value
// per request and trivially bypass the rate limiter (CWE-290).
let ip = request.ip;
if (!ip && request.socket.remoteAddress) {
logger.warn("request.ip is missing");
ip = request.socket.remoteAddress;
}
// If resolution stopped at a Cloudflare edge address (X-Forwarded-For no
// longer contains the visitor, e.g. after a Cloudflare-side change), fall
// back to cf-connecting-ip. This is safe: request.ip can only be a
// Cloudflare address when every hop up to it is trusted, i.e. the request
// genuinely traversed Cloudflare. A direct client forging the header is
// keyed by its own untrusted address instead, so the CWE-290 bypass above
// does not apply.
if (ip && isCloudflareIP(ip)) {
const cfConnectingIP = request.headers["cf-connecting-ip"];
if (typeof cfConnectingIP === "string" && cfConnectingIP) {
ip = cfConnectingIP.trim();
}
}
// remove port number from IPv4 addresses
return (ip || "").replace(/:\d+[^:]*$/, "");
}
const rate = rateLimit({
store: new RedisStore({
sendCommand: (...args: string[]) => redisClient.sendCommand(args),
@@ -201,7 +171,7 @@ export default async function start() {
}
return false;
},
max: async (request: express.Request, _response: express.Response) => {
limit: async (request: express.Request, _response: express.Response) => {
try {
const user = await getUser(request);
if (user) return config.RATE_LIMIT;
@@ -211,8 +181,8 @@ export default async function start() {
// if not logged in, limit to half the rate
return config.RATE_LIMIT / 2;
},
keyGenerator,
standardHeaders: true,
keyGenerator: requestRateLimitKey,
standardHeaders: "draft-6",
legacyHeaders: false,
message: (_request: express.Request, _response: express.Response) => {
return `You can only make ${config.RATE_LIMIT} requests every 15min. Please try again later.`;
@@ -223,14 +193,14 @@ export default async function start() {
delayAfter: 50,
delayMs: () => 150,
maxDelayMs: 5000,
keyGenerator,
keyGenerator: requestRateLimitKey,
});
const webViewSpeedLimiter = slowDown({
windowMs: 15 * 60 * 1000, // 15 minutes
delayAfter: 200,
delayMs: () => 150,
maxDelayMs: 5000,
keyGenerator,
keyGenerator: requestRateLimitKey,
});
app.use("/github", rate, speedLimiter, githubAppRouter);
+16
View File
@@ -0,0 +1,16 @@
import { Request } from "express";
import { ipKeyGenerator } from "express-rate-limit";
import { isCloudflareIP } from "./trustProxy";
export function requestRateLimitKey(request: Request): string {
// Express resolves request.ip using the configured trusted proxies.
let ip = request.ip || request.socket.remoteAddress || "";
// Only accept Cloudflare's visitor header when resolution reached its edge.
if (isCloudflareIP(ip)) {
const visitor = request.headers["cf-connecting-ip"];
if (typeof visitor === "string" && visitor.trim()) ip = visitor.trim();
}
// Some proxies append a port to IPv4 addresses. Do not truncate IPv6.
ip = ip.replace(/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/, "$1");
return ipKeyGenerator(ip);
}
+3 -3
View File
@@ -600,15 +600,15 @@ router.delete("/errors", async (req, res) => {
// SCAN the hourly counter keys and del them along with the list and
// dropped counter so the admin page comes back to a clean slate.
const hourlyKeys: string[] = [];
let cursor = 0;
let cursor = "0";
do {
const reply = await client.scan(cursor, {
MATCH: `${ERROR_LOG_HOURLY_PREFIX}*`,
COUNT: 100,
});
cursor = Number(reply.cursor);
cursor = reply.cursor;
for (const k of reply.keys) hourlyKeys.push(k);
} while (cursor !== 0);
} while (cursor !== "0");
const pipe = client.multi();
pipe.del(ERROR_LOG_KEY);
pipe.del(ERROR_LOG_DROPPED_KEY);
+1 -2
View File
@@ -1,7 +1,7 @@
import { createClient } from "redis";
import * as passport from "passport";
import * as session from "express-session";
import RedisStore from "connect-redis";
import { RedisStore } from "connect-redis";
import * as OAuth2Strategy from "passport-oauth2";
import { Profile, Strategy } from "passport-github2";
import * as express from "express";
@@ -166,7 +166,6 @@ passport.deserializeUser(async (id: string, done) => {
export function initSession() {
const redisClient = createClient({
legacyMode: false,
socket: {
port: config.REDIS_PORT,
host: config.REDIS_HOSTNAME,
+1 -1
View File
@@ -189,7 +189,7 @@ async function webView(
if (f.extension() == "md") {
try {
const content = await streamToString(await f.anonymizedContent());
const body = sanitizeHtml(marked.marked(content, { headerIds: false, mangle: false }), sanitizeOptions);
const body = sanitizeHtml(marked.marked.parse(content, { async: false }), sanitizeOptions);
const html = `<!DOCTYPE html><html><head><title>Content</title></head><link rel="stylesheet" href="/css/all.min.css" /><body><div class="container p-3 file-content markdown-body">${body}</div></body></html>`;
res.contentType("text/html").send(html);
} catch {
+81
View File
@@ -0,0 +1,81 @@
/* global fetch */
const { expect } = require("chai");
const express = require("express");
const session = require("express-session");
const { createClient } = require("redis");
const { RedisStore: SessionStore } = require("connect-redis");
const { RedisStore: RateLimitStore } = require("rate-limit-redis");
const { rateLimit } = require("express-rate-limit");
const { execFile } = require("node:child_process");
const { promisify } = require("node:util");
const path = require("node:path");
const port = Number(process.env.PERFORMANCE_REDIS_PORT);
(port ? describe : describe.skip)("upgraded Redis stores and session cleanup", function () {
this.timeout(15000);
let redis, server, base;
const prefix = `dependency-test:${process.pid}:`;
before(async function () {
redis = createClient({ socket: { host: "127.0.0.1", port, reconnectStrategy: false } });
redis.on("error", () => {});
await redis.connect();
const app = express();
app.use(session({ secret: "dependency-test", resave: false, saveUninitialized: false,
store: new SessionStore({ client: redis, prefix: `${prefix}session:` }) }));
app.get("/session", (req, res) => {
req.session.visits = (req.session.visits || 0) + 1;
res.json({ visits: req.session.visits });
});
const limiter = () => rateLimit({ windowMs: 60000, limit: 1, keyGenerator: () => "client",
store: new RateLimitStore({ prefix: `${prefix}limit:`, sendCommand: (...args) => redis.sendCommand(args) }) });
app.get("/first", limiter(), (_req, res) => res.send("ok"));
app.get("/second", limiter(), (_req, res) => res.send("ok"));
await new Promise(resolve => { server = app.listen(0, "127.0.0.1", resolve); });
base = `http://127.0.0.1:${server.address().port}`;
});
after(async function () {
if (server) {
server.closeAllConnections();
await new Promise(resolve => server.close(resolve));
}
if (redis?.isOpen) {
for await (const keys of redis.scanIterator({ MATCH: `${prefix}*` })) {
if (keys.length) await redis.del(keys);
}
await redis.del([`anoGH_session:${prefix}legacy`, `anoGH_session:${prefix}current`]);
await redis.close();
}
});
it("persists sessions across HTTP requests", async function () {
const first = await fetch(`${base}/session`);
expect(await first.json()).to.deep.equal({ visits: 1 });
const cookie = first.headers.get("set-cookie").split(";")[0];
const second = await fetch(`${base}/session`, { headers: { Cookie: cookie } });
expect(await second.json()).to.deep.equal({ visits: 2 });
});
it("shares a request quota between separate Redis store instances", async function () {
expect((await fetch(`${base}/first`)).status).to.equal(200);
expect((await fetch(`${base}/second`)).status).to.equal(429);
});
it("scans session batches, preserves current sessions and applies legacy cleanup", async function () {
const legacy = `anoGH_session:${prefix}legacy`, current = `anoGH_session:${prefix}current`;
await redis.set(legacy, JSON.stringify({ passport: { user: { id: "legacy" } } }));
await redis.set(current, JSON.stringify({ passport: { user: "current" } }));
const run = async args => {
const result = await promisify(execFile)(process.execPath,
["-r", "ts-node/register", "src/scripts/purge-legacy-sessions.ts", ...args], {
cwd: path.resolve(__dirname, ".."), timeout: 10000,
env: { ...process.env, NODE_ENV: "test", REDIS_HOSTNAME: "127.0.0.1", REDIS_PORT: String(port) },
});
return JSON.parse(result.stdout.trim().split("\n").at(-1));
};
expect(await run([])).to.deep.equal({ found: 1, removed: 0 });
expect(await redis.exists(legacy)).to.equal(1);
expect(await run(["--apply"])).to.deep.equal({ found: 1, removed: 1 });
expect(await redis.exists(legacy)).to.equal(0);
expect(await redis.get(current)).to.equal(JSON.stringify({ passport: { user: "current" } }));
});
});
+86
View File
@@ -0,0 +1,86 @@
/* global fetch */
const { expect } = require("chai");
const http = require("node:http");
const net = require("node:net");
const path = require("node:path");
const { spawn } = require("node:child_process");
const { once } = require("node:events");
const { setTimeout, clearTimeout } = require("node:timers");
describe("development proxy", function () {
this.timeout(15000);
let upstream, child, base, target;
before(async function () {
upstream = http.createServer((req, res) => {
if (req.url === "/api/headers") {
res.setHeader("content-type", "application/json");
return res.end(JSON.stringify({ origin: req.headers.origin, referer: req.headers.referer }));
}
res.setHeader("set-cookie", "test=value; Domain=localhost; Secure; SameSite=None; Path=/");
if (req.url === "/api/redirect") {
res.writeHead(302, { location: `${target}/api/next` });
return res.end();
}
res.setHeader("content-type", "text/html");
res.end(`<a href="${target}/api/next">next</a>`);
});
await new Promise(resolve => upstream.listen(0, "127.0.0.1", resolve));
target = `http://127.0.0.1:${upstream.address().port}`;
const socket = net.createServer();
await new Promise(resolve => socket.listen(0, "127.0.0.1", resolve));
const port = socket.address().port;
await new Promise(resolve => socket.close(resolve));
base = `http://127.0.0.1:${port}`;
child = spawn(process.execPath, ["scripts/dev-proxy.js"], {
cwd: path.resolve(__dirname, ".."), env: { ...process.env, UPSTREAM: target, PORT: String(port) },
stdio: ["ignore", "pipe", "pipe"],
});
await new Promise((resolve, reject) => {
let output = "", errors = "";
const timer = setTimeout(() => reject(new Error("Development proxy startup timed out")), 10000);
child.stderr.on("data", chunk => { errors += chunk; });
child.stdout.on("data", chunk => {
output += chunk;
if (output.includes("dev-proxy")) { clearTimeout(timer); resolve(); }
});
child.once("exit", code => { clearTimeout(timer); reject(new Error(`Proxy exited with ${code}: ${errors}`)); });
});
});
after(async function () {
if (child && child.exitCode === null && child.signalCode === null) {
const exited = once(child, "exit");
child.kill();
await exited;
}
if (upstream) {
upstream.closeAllConnections();
await new Promise(resolve => upstream.close(resolve));
}
});
it("forwards the request hooks to the upstream", async function () {
const response = await fetch(`${base}/api/headers`);
expect(response.status).to.equal(200);
expect(await response.json()).to.deep.equal({ origin: target, referer: `${target}/api/headers` });
});
it("rewrites HTML, cookies and redirects for localhost", async function () {
const response = await fetch(`${base}/api/html`);
expect(await response.text()).to.equal('<a href="/api/next">next</a>');
const cookie = response.headers.get("set-cookie");
expect(cookie).to.include("SameSite=Lax");
expect(cookie).not.to.match(/Secure|Domain=/i);
const redirect = await fetch(`${base}/api/redirect`, { redirect: "manual" });
expect(redirect.status).to.equal(302);
expect(redirect.headers.get("location")).to.equal("/api/next");
});
it("serves the local UI with resolved asset names", async function () {
const response = await fetch(base);
expect(response.status).to.equal(200);
const body = await response.text();
expect(body).to.include('id="app"');
expect(body).not.to.include("__CORE_JS__");
expect(body).not.to.include("__VENDOR_JS__");
});
});
+3 -3
View File
@@ -65,13 +65,13 @@ describe("download headers and website revalidation", function () {
expect(second.headers["content-security-policy"]).to.include("sandbox");
});
it("renders Markdown again after a transient failure served the raw fallback", async function () {
const marked = require("marked"), render = marked.marked;
const marked = require("marked").marked, render = marked.parse;
let first;
try {
marked.marked = () => { throw Error("transient rendering failure"); };
marked.parse = () => { throw Error("transient rendering failure"); };
first = await fetch("/w/fixture/page.md");
expect(first.status).to.equal(200); expect(first.body).to.equal("fixture content");
} finally { marked.marked = render; }
} finally { marked.parse = render; }
const recovered = await fetch("/w/fixture/page.md", "GET", { "If-None-Match": first.headers.etag });
expect(recovered.status).to.equal(200); expect(recovered.body).to.include("Anonymized page");
expect(recovered.headers.etag).not.to.equal(first.headers.etag);
+1 -1
View File
@@ -34,7 +34,7 @@ const sanitizeOptions = {
* sanitizeHtml(marked(content, opts), sanitizeOptions)
*/
function renderAndSanitize(markdown) {
const raw = marked(markdown, { headerIds: false, mangle: false });
const raw = marked(markdown, { async: false });
return sanitizeHtml(raw, sanitizeOptions);
}
@@ -18,12 +18,12 @@ const port = Number(process.env.PERFORMANCE_REDIS_PORT);
});
beforeEach(async function () {
const keys = [];
for await (const key of redis.scanIterator({ MATCH: "performance:v1:*" })) keys.push(key);
for await (const batch of redis.scanIterator({ MATCH: "performance:v1:*" })) keys.push(...batch);
if (keys.length) await redis.del(keys);
requestMetrics.drain();
});
afterEach(async function () { if (stop) { await stop(); stop = null; } requestMetrics.drain(); });
after(async function () { await redis.disconnect(); config.REDIS_HOSTNAME = previous.host; config.REDIS_PORT = previous.port; });
after(async function () { await redis.close(); config.REDIS_HOSTNAME = previous.host; config.REDIS_PORT = previous.port; });
async function start(service) {
stop = startPerformanceMonitoring(service, () => ({ running: 0, waiting: 0 }));
const deadline = Date.now() + 4000;
+1 -1
View File
@@ -26,7 +26,7 @@ const { setTimeout: delay } = require("node:timers/promises");
redis = createClient({ socket: { host: "127.0.0.1", port: config.REDIS_PORT } });
await redis.connect();
});
after(async () => { await closeCacheRedis(); await redis.quit(); Object.assign(config, previousRedis); await fs.rm(root, { force: true, recursive: true }); });
after(async () => { await closeCacheRedis(); await redis.close(); Object.assign(config, previousRedis); await fs.rm(root, { force: true, recursive: true }); });
it("downloads and publishes once across two streamer processes and ten callers", async () => {
let producers = 0;
const children = Array.from({ length: 2 }, () => fork(path.join(__dirname, "fixtures/performance-cache-worker.js"), [], { stdio: ["ignore", "ignore", "ignore", "ipc"], env: { ...process.env,
@@ -32,8 +32,8 @@ const uri = process.env.PERFORMANCE_MONGO_URI;
beforeEach(async () => { await Repo.deleteMany({}); });
const missing = async file => { try { await fs.access(file); return false; } catch (error) { if (error.code === "ENOENT") return true; throw error; } };
async function waitFor(check) {
const deadline = Date.now() + 5000;
async function waitFor(check, timeoutMs = 5000) {
const deadline = Date.now() + timeoutMs;
while (!await check()) { if (Date.now() >= deadline) throw Error("Streamer cleanup did not finish"); await new Promise(resolve => setTimeout(resolve, 20)); }
}
async function launch(invalidCredentials = false, holdConnect = false) {
@@ -78,7 +78,9 @@ const uri = process.env.PERFORMANCE_MONGO_URI;
it("connects before serving and rejects a fill published after expiration in the actual streamer entrypoint", async () => {
const runtime = await launch(false, true); let pending;
try {
await waitFor(() => runtime.messages.some(message => message.connectionHeld));
// Loading TypeScript and the Redis client in a fresh process can take
// longer than the cleanup deadline used after the streamer starts.
await waitFor(() => runtime.messages.some(message => message.connectionHeld), 10000);
await fetch(runtime.url + "/healthcheck").then(() => { throw Error("Streamer served before connecting"); }, error => expect(error).to.be.instanceOf(Error));
runtime.child.send("connect");
await runtime.ready;
+1 -1
View File
@@ -110,7 +110,7 @@ describe("large anonymization resources", function () {
});
it("bounds an unresponsive Redis command and disconnects its socket", async () => {
let disconnected = false, error;
const client = { isOpen: true, disconnect: async () => { disconnected = true; } };
const client = { isOpen: true, destroy: () => { disconnected = true; } };
try { await cacheCommand(new Promise(() => {}), client, 20); } catch (failure) { error = failure; }
expect(error.message).to.equal("cache_command_timeout"); expect(disconnected).to.equal(true);
});
+47
View File
@@ -0,0 +1,47 @@
/* global fetch */
const { expect } = require("chai");
const express = require("express");
const { rateLimit } = require("express-rate-limit");
require("ts-node/register/transpile-only");
const { requestRateLimitKey } = require("../src/server/rate-limit-key");
describe("rate limiter client addresses", function () {
const key = (ip, headers = {}, remoteAddress = "127.0.0.1") =>
requestRateLimitKey({ ip, headers, socket: { remoteAddress } });
it("normalizes IPv4 ports and mapped IPv6 without truncating native IPv6", function () {
expect(key("192.0.2.1:1234")).to.equal("192.0.2.1");
expect(key("::ffff:192.0.2.1")).to.equal("192.0.2.1");
expect(key("2001:db8:abcd:1200::1234")).to.equal("2001:db8:abcd:1200::/56");
expect(key("2001:db8:abcd:1300::1234")).not.to.equal(key("2001:db8:abcd:1200::1234"));
});
it("accepts the visitor header only for a Cloudflare edge address", function () {
expect(key("192.0.2.1", { "cf-connecting-ip": "198.51.100.1" })).to.equal("192.0.2.1");
expect(key("104.16.0.1", { "cf-connecting-ip": " 198.51.100.1 " })).to.equal("198.51.100.1");
expect(key("104.16.0.1", { "cf-connecting-ip": " " })).to.equal("104.16.0.1");
expect(key(undefined, {}, "::ffff:192.0.2.1")).to.equal("192.0.2.1");
});
it("shares limits across IPv6 addresses within a subnet and ignores forged Cloudflare headers", async function () {
const app = express();
app.set("trust proxy", "loopback");
app.use(rateLimit({ windowMs: 60000, limit: 1, keyGenerator: requestRateLimitKey }));
app.get("/", (_req, res) => res.send("ok"));
let server;
await new Promise(resolve => { server = app.listen(0, "127.0.0.1", resolve); });
const request = (ip, visitor) => fetch(`http://127.0.0.1:${server.address().port}/`, {
headers: { "x-forwarded-for": ip, "cf-connecting-ip": visitor },
});
try {
expect((await request("2001:db8:abcd:1200::1", "198.51.100.1")).status).to.equal(200);
expect((await request("2001:db8:abcd:12ff::2", "198.51.100.2")).status).to.equal(429);
expect((await request("2001:db8:abcd:1300::1", "198.51.100.1")).status).to.equal(200);
expect((await request("192.0.2.1", "198.51.100.1")).status).to.equal(200);
expect((await request("::ffff:192.0.2.1", "198.51.100.2")).status).to.equal(429);
} finally {
server.closeAllConnections();
await new Promise(resolve => server.close(resolve));
}
});
});
+12 -9
View File
@@ -1,9 +1,10 @@
const { expect } = require("chai");
const { JSDOM, VirtualConsole, ResourceLoader } = require("jsdom");
const { JSDOM, VirtualConsole, requestInterceptor } = require("jsdom");
const fs = require("fs");
const path = require("path");
const { URL } = require("node:url");
const { setTimeout: delay } = require("node:timers/promises");
const { Response } = globalThis;
const publicDir = path.join(__dirname, "../public");
const bundles = ["core.min.js", "vendor.min.js"].map(name => fs.readFileSync(path.join(publicDir, "script", name), "utf8"));
@@ -12,19 +13,21 @@ async function browser(route = "/", overrides = {}, storage = {}) {
const errors = [], requests = [], assets = [];
const virtualConsole = new VirtualConsole();
virtualConsole.on("jsdomError", error => {
if (!error.message.includes("navigation (except hash changes)")) errors.push(error.message);
if (error.type === "not-implemented" && error.message === "Not implemented: navigation to another Document") return;
errors.push(error.message);
});
virtualConsole.on("error", error => errors.push(error?.message || String(error)));
const dom = new JSDOM('<!doctype html><html><head></head><body><div id="app"></div></body></html>', {
resources: new class extends ResourceLoader {
fetch(url) {
resources: {
interceptors: [requestInterceptor(request => {
const url = request.url;
assets.push(new URL(url).pathname);
if (/\/pdf\.[a-f0-9]+\.min\.js$/.test(new URL(url).pathname) && dom.window.pdfjsLib) return Promise.resolve(Buffer.from(""));
if (/\/pdf\.[a-f0-9]+\.min\.js$/.test(new URL(url).pathname) && dom.window.pdfjsLib) return new Response("");
const pathname = new URL(url).pathname.replace(/\.[a-f0-9]{10}\.min\.js$/, ".min.js");
if (pathname.startsWith("/script/")) return Promise.resolve(fs.readFileSync(path.join(publicDir, pathname)));
return null;
}
}(),
if (pathname.startsWith("/script/")) return new Response(fs.readFileSync(path.join(publicDir, pathname)), { headers: { "content-type": "application/javascript" } });
return new Response(null, { status: 204 });
})],
},
url: "http://localhost" + route, runScripts: "dangerously", pretendToBeVisual: true, virtualConsole,
});
const window = dom.window;