texlite 0.6.0 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +1 -0
  2. package/dist/client/assets/{BinaryModule-Aj1vLnwf-C2TCdTPg.js → BinaryModule-Aj1vLnwf-FnkCpIDV.js} +1 -1
  3. package/dist/client/assets/{GitDialog-9mNxx8oX.js → GitDialog-BtWtaeZV.js} +1 -1
  4. package/dist/client/assets/{HistoryDialog-CmoD9Cvf.js → HistoryDialog-DOzAQAQA.js} +1 -1
  5. package/dist/client/assets/{LatexEditor-Dp8EoZBP.js → LatexEditor-C5S1jijC.js} +20 -20
  6. package/dist/client/assets/PdfPreview-B3-kvd02.js +1 -0
  7. package/dist/client/assets/{ProjectNavigationDialogs-veKVnRxc.js → ProjectNavigationDialogs-DeEMFRsp.js} +1 -1
  8. package/dist/client/assets/{SystemMetricsDialog-D_oiVmBC.js → SystemMetricsDialog-DWh6yS0w.js} +1 -1
  9. package/dist/client/assets/binary-D_GeVl1T.js +1 -0
  10. package/dist/client/assets/index-C4KkkecE.js +17 -0
  11. package/dist/client/assets/index-DwzHKaKD.css +1 -0
  12. package/dist/client/assets/{index-CyEG4l54.js → index-nywS6bYC.js} +1 -1
  13. package/dist/client/assets/{plus-Bup73Wq6.js → plus-B_Bl12ZY.js} +1 -1
  14. package/dist/client/assets/{spellCheck-BHW5bumN.js → spellCheck-BynsoX26.js} +3 -3
  15. package/dist/client/index.html +2 -2
  16. package/dist/server/app.js +888 -339
  17. package/dist/server/archive.js +20 -3
  18. package/dist/server/auth.js +13 -2
  19. package/dist/server/collaboration.js +815 -124
  20. package/dist/server/compileArtifacts.js +31 -13
  21. package/dist/server/compiler.js +69 -20
  22. package/dist/server/db.js +6 -0
  23. package/dist/server/environment.js +4 -2
  24. package/dist/server/files.js +172 -25
  25. package/dist/server/git.js +152 -124
  26. package/dist/server/history.js +86 -18
  27. package/dist/server/http.js +26 -0
  28. package/dist/server/index.js +39 -26
  29. package/dist/server/instanceLock.js +137 -0
  30. package/dist/server/latexCompletion.js +23 -1
  31. package/dist/server/latexFormatter.js +4 -2
  32. package/dist/server/processTree.js +31 -0
  33. package/dist/server/projectMutations.js +255 -0
  34. package/dist/server/projectOutline.js +113 -1
  35. package/dist/server/projectSearch.js +6 -0
  36. package/dist/server/routes/compile.js +505 -128
  37. package/dist/server/security.js +61 -1
  38. package/dist/server/synctex.js +11 -2
  39. package/dist/server/zip.js +2 -1
  40. package/package.json +1 -1
  41. package/dist/client/assets/PdfPreview-IfkmLMSU.js +0 -1
  42. package/dist/client/assets/binary-DTriWQzf.js +0 -1
  43. package/dist/client/assets/index-CI87C5Wy.js +0 -17
  44. package/dist/client/assets/index-DB6GC33g.css +0 -1
@@ -5,7 +5,10 @@ const KEY_LENGTH = 64;
5
5
  export const MIN_PASSWORD_LENGTH = 8;
6
6
  export async function hashPassword(password) {
7
7
  if (password.length < MIN_PASSWORD_LENGTH) {
8
- throw new Error(`密码至少需要 ${MIN_PASSWORD_LENGTH} 个字符`);
8
+ throw Object.assign(new Error(`密码至少需要 ${MIN_PASSWORD_LENGTH} 个字符`), {
9
+ statusCode: 400,
10
+ code: "PASSWORD_TOO_SHORT"
11
+ });
9
12
  }
10
13
  const salt = randomBytes(16);
11
14
  const key = (await scrypt(password, salt, KEY_LENGTH));
@@ -27,3 +30,60 @@ export function createSessionToken() {
27
30
  export function digestToken(token) {
28
31
  return createHash("sha256").update(token).digest("hex");
29
32
  }
33
+ export class LoginRateLimiter {
34
+ attempts = new Map();
35
+ maxAttempts;
36
+ windowMs;
37
+ lockoutMs;
38
+ constructor(options = {}) {
39
+ this.maxAttempts = options.maxAttempts ?? 5;
40
+ this.windowMs = options.windowMs ?? 15 * 60_000;
41
+ this.lockoutMs = options.lockoutMs ?? 15 * 60_000;
42
+ }
43
+ isLocked(key) {
44
+ const now = Date.now();
45
+ const record = this.attempts.get(key);
46
+ if (!record)
47
+ return false;
48
+ if (record.lockedUntilMs > now)
49
+ return true;
50
+ if (now - record.firstAttemptMs > this.windowMs && record.lockedUntilMs <= now) {
51
+ this.attempts.delete(key);
52
+ return false;
53
+ }
54
+ return false;
55
+ }
56
+ recordFailure(key) {
57
+ const now = Date.now();
58
+ let record = this.attempts.get(key);
59
+ if (!record || (now - record.firstAttemptMs > this.windowMs && record.lockedUntilMs <= now)) {
60
+ record = { count: 1, firstAttemptMs: now, lockedUntilMs: 0 };
61
+ this.attempts.set(key, record);
62
+ return { locked: false, remainingAttempts: this.maxAttempts - 1, retryAfterSeconds: 0 };
63
+ }
64
+ record.count += 1;
65
+ if (record.count >= this.maxAttempts) {
66
+ record.lockedUntilMs = now + this.lockoutMs;
67
+ return { locked: true, remainingAttempts: 0, retryAfterSeconds: Math.ceil(this.lockoutMs / 1000) };
68
+ }
69
+ return {
70
+ locked: false,
71
+ remainingAttempts: Math.max(0, this.maxAttempts - record.count),
72
+ retryAfterSeconds: 0
73
+ };
74
+ }
75
+ reset(key) {
76
+ this.attempts.delete(key);
77
+ }
78
+ clear() {
79
+ this.attempts.clear();
80
+ }
81
+ prune() {
82
+ const now = Date.now();
83
+ for (const [key, record] of this.attempts.entries()) {
84
+ if (record.lockedUntilMs <= now && now - record.firstAttemptMs > this.windowMs) {
85
+ this.attempts.delete(key);
86
+ }
87
+ }
88
+ }
89
+ }
@@ -2,6 +2,15 @@ import path from "node:path";
2
2
  import { execFile } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  const runFile = promisify(execFile);
5
+ /** A user-correctable SyncTeX failure, rather than an opaque server error. */
6
+ export class SyncTeXError extends Error {
7
+ statusCode = 422;
8
+ code = "SYNCTEX_FAILED";
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "SyncTeXError";
12
+ }
13
+ }
5
14
  async function runSynctex(args, cwd) {
6
15
  try {
7
16
  const { stdout } = await runFile("synctex", args, {
@@ -12,7 +21,7 @@ async function runSynctex(args, cwd) {
12
21
  catch (error) {
13
22
  const stderr = typeof error === "object" && error !== null && "stderr" in error
14
23
  ? String(error.stderr) : "";
15
- throw new Error(stderr.trim() || "SyncTeX 无法定位该位置,请重新编译项目");
24
+ throw new SyncTeXError(stderr.trim() || "SyncTeX 无法定位该位置,请重新编译项目");
16
25
  }
17
26
  }
18
27
  export async function sourceToPdf(sourceDirectory, pdfPath, sourcePath, line, column) {
@@ -44,7 +53,7 @@ export async function pdfToSource(sourceDirectory, pdfPath, page, x, y) {
44
53
  const line = numericField(output, "Line");
45
54
  const column = numericField(output, "Column");
46
55
  if (!input || !line)
47
- throw new Error("SyncTeX 没有找到对应的源码位置");
56
+ throw new SyncTeXError("SyncTeX 没有找到对应的源码位置");
48
57
  const absoluteInput = path.isAbsolute(input) ? input : path.resolve(sourceDirectory, input);
49
58
  return { input: absoluteInput, line, column: column && column > 0 ? column : 1 };
50
59
  }
@@ -3,13 +3,14 @@ import path from "node:path";
3
3
  import { Transform } from "node:stream";
4
4
  import { pipeline } from "node:stream/promises";
5
5
  import yauzl from "yauzl";
6
- import { safeRelativePath } from "./files.js";
6
+ import { assertNoSymbolicLinks, safeRelativePath } from "./files.js";
7
7
  const MAX_ENTRIES = 1_000;
8
8
  const MAX_TOTAL_BYTES = 200 * 1024 * 1024;
9
9
  export async function extractProjectZip(buffer, destination, maxFileBytes) {
10
10
  const maxTotalBytes = Math.max(MAX_TOTAL_BYTES, maxFileBytes);
11
11
  const entries = await inspectZip(buffer, maxFileBytes, maxTotalBytes);
12
12
  const prefix = commonRootPrefix(entries.filter((entry) => !entry.directory).map((entry) => entry.fileName));
13
+ assertNoSymbolicLinks(destination);
13
14
  fs.mkdirSync(destination, { recursive: true, mode: 0o700 });
14
15
  const files = await extractZip(buffer, destination, prefix, maxFileBytes, maxTotalBytes);
15
16
  const mainFile = discoverMainFile(destination, files);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "texlite",
3
- "version": "0.6.0",
3
+ "version": "0.6.6",
4
4
  "description": "A lightweight local-first collaborative LaTeX workspace",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- import{u as V,r as i,j as n}from"./react-CsO_xvbl.js";import{g as Z,P as O,w as U,G as Y}from"./pdf-DPnwNQHb.js";import{M as F,P as X,a as H}from"./plus-Bup73Wq6.js";import{L as T}from"./index-CI87C5Wy.js";Y.workerSrc=U;const q=new O;function te({url:E,target:u,compiling:N=!1,onViewportLocation:k,onDoubleClickLocation:h}){const{t}=V(),p=i.useRef(null),[f,L]=i.useState(null),[b,x]=i.useState(0),[o,m]=i.useState(100),[P,y]=i.useState(""),z=i.useRef(new Map),v=i.useCallback((e,r)=>{r?z.current.set(e,r):z.current.delete(e)},[]),S=async e=>{if(!f||e==null)return;let r=e;if(typeof e=="string"&&(r=await f.getDestination(e).catch(()=>null)),!Array.isArray(r)||r.length===0)return;const d=r[0];let l=null;if(typeof d=="number"&&Number.isInteger(d))l=d;else if(d!=null)try{l=await f.getPageIndex(d)}catch{return}l==null||l<0||l>=f.numPages||z.current.get(l+1)?.scrollIntoView({behavior:"smooth",block:"start",inline:"nearest"})},R=()=>{const e=p.current;if(!e)return;const r=e.getBoundingClientRect(),d=r.left+r.width/2,l=r.top+r.height/2,D=[...e.querySelectorAll(".pdf-page")],s=D.find(j=>{const C=j.getBoundingClientRect();return l>=C.top&&l<=C.bottom})??D.reduce((j,C)=>{if(!j)return C;const $=Math.abs(C.getBoundingClientRect().top-l),B=Math.abs(j.getBoundingClientRect().top-l);return $<B?C:j},null);if(!s)return;const a=s.getBoundingClientRect(),c=Number(s.dataset.scale)||1,M=Number(s.dataset.page),W=Math.max(0,Math.min(a.width,d-a.left))/c,A=Math.max(0,Math.min(a.height,l-a.top))/c;M&&k(M,W,A)};i.useEffect(()=>{const e=p.current;if(!e)return;const r=()=>x(e.clientWidth);r();const d=new ResizeObserver(r);return d.observe(e),()=>d.disconnect()},[]),i.useEffect(()=>{let e=!1;L(null),y("");const r=Z({url:E,worker:q});return r.promise.then(d=>{e||L(d)}).catch(()=>{e||y(t("editor.pdfLoadFailed"))}),()=>{e=!0,r.destroy()}},[E]),i.useEffect(()=>{o>100||!p.current||(p.current.scrollLeft=0)},[o,b,f]);const g=e=>m(r=>Math.min(200,Math.max(50,r+e))),I=()=>{p.current&&(p.current.scrollLeft=0),m(100)},w=e=>{!e.shiftKey||Math.abs(e.deltaX)>Math.abs(e.deltaY)||(e.preventDefault(),e.currentTarget.scrollLeft+=e.deltaY,R())};return n.jsxs("div",{className:"pdf-viewer",children:[n.jsxs("div",{className:"pdf-toolbar",role:"toolbar","aria-label":t("editor.pdfZoomControls"),children:[n.jsx("button",{disabled:o<=50,title:t("editor.pdfZoomOut"),"aria-label":t("editor.pdfZoomOut"),onClick:()=>g(-10),children:n.jsx(F,{size:14})}),n.jsxs("span",{className:"pdf-zoom-value","aria-live":"polite",children:[o,"%"]}),n.jsx("button",{disabled:o>=200,title:t("editor.pdfZoomIn"),"aria-label":t("editor.pdfZoomIn"),onClick:()=>g(10),children:n.jsx(X,{size:14})}),n.jsxs("button",{className:"pdf-fit-width",title:t("editor.pdfFitWidth"),onClick:I,children:[n.jsx(H,{size:13}),t("editor.pdfFitWidth")]})]}),N&&n.jsxs("div",{className:"pdf-compiling-overlay",role:"status","aria-live":"polite",children:[n.jsx(T,{className:"spin",size:20}),n.jsx("span",{children:t("editor.compiling")})]}),n.jsx("div",{className:"pdf-document",ref:p,onScroll:R,onWheel:w,children:n.jsxs("div",{className:"pdf-pages",style:{width:`${Math.max(1,b-28)*Math.max(1,o/100)}px`},children:[!f&&!P&&n.jsxs("div",{className:"pdf-loading",role:"status","aria-live":"polite",children:[n.jsx(T,{className:"spin",size:24}),n.jsx("span",{children:t("editor.loadingPdf")})]}),P&&n.jsx("div",{className:"preview-empty",children:n.jsx("strong",{children:P})}),f&&b>0&&Array.from({length:f.numPages},(e,r)=>n.jsx(G,{document:f,pageNumber:r+1,availableWidth:b-28,zoom:o/100,target:u?.page===r+1?u:null,onReady:R,onPageElement:v,onInternalLink:S,onDoubleClickLocation:h},r+1))]})})]})}function G({document:E,pageNumber:u,availableWidth:N,zoom:k,target:h,onReady:t,onPageElement:p,onInternalLink:f,onDoubleClickLocation:L}){const b=i.useRef(null),x=i.useRef(null),o=i.useRef(null),[m,P]=i.useState(1),[y,z]=i.useState({width:0,height:0}),[v,S]=i.useState(u===1),[R,g]=i.useState(!1),[I,w]=i.useState(null),[e,r]=i.useState([]);i.useEffect(()=>{const s=b.current;if(!s||v)return;if(!("IntersectionObserver"in window)){S(!0);return}const a=new IntersectionObserver(c=>{c.some(M=>M.isIntersecting)&&(S(!0),a.disconnect())},{rootMargin:"900px 0px"});return a.observe(s),()=>a.disconnect()},[v]),i.useEffect(()=>{h&&S(!0)},[h?.nonce]),i.useEffect(()=>{if(!v)return;let s=!1,a=null;return g(!1),w(null),r([]),E.getPage(u).then(c=>{if(s)return;const M=c.getViewport({scale:1}),W=N/M.width,A=Math.min(3.3,Math.max(.175,W*k)),j=c.getViewport({scale:A});if(P(A),z({width:j.width,height:j.height}),w(j),c.getAnnotations({intent:"display"}).then(B=>{s||r(B)}).catch(()=>{s||r([])}),!x.current)return;const C=Math.max(1,window.devicePixelRatio||1),$=c.getViewport({scale:A*C});return x.current.width=Math.floor($.width),x.current.height=Math.floor($.height),a=c.render({canvas:x.current,viewport:$}),a.promise.then(()=>{s||(g(!0),window.requestAnimationFrame(t))})}).catch(()=>{}),()=>{s=!0,a?.cancel()}},[E,u,N,k,v]),i.useEffect(()=>{!h||!o.current||o.current.scrollIntoView({behavior:"smooth",block:"center",inline:"center"})},[h?.nonce,m]);const d=s=>{if(!L||!R||s.target instanceof HTMLElement&&s.target.closest("a"))return;const a=x.current;if(!a)return;const c=a.getBoundingClientRect();if(s.clientX<c.left||s.clientX>c.right||s.clientY<c.top||s.clientY>c.bottom)return;const M=m||1,W=Math.max(0,Math.min(c.width,s.clientX-c.left))/M,A=Math.max(0,Math.min(c.height,s.clientY-c.top))/M;L(u,W,A)},l=Math.max(1,N*k),D=l*(792/612);return n.jsxs("figure",{ref:s=>{b.current=s,p(u,s)},onDoubleClick:d,className:`pdf-page${R?" rendered":" loading"}`,"data-page":u,"data-scale":m,style:{width:`${y.width||l}px`,minHeight:`${y.height||D}px`},children:[n.jsx("canvas",{ref:x,style:{width:`${y.width||l}px`,height:`${y.height||D}px`}}),R&&I&&n.jsx(_,{annotations:e,viewport:I,onInternalLink:f}),h&&n.jsx("span",{ref:o,className:"pdf-sync-marker",style:{left:h.x*m,top:h.y*m}}),n.jsx("figcaption",{children:u})]})}function _({annotations:E,viewport:u,onInternalLink:N}){const{t:k}=V(),h=k("editor.pdfLink");return n.jsx("div",{className:"pdf-annotation-layer","aria-label":k("editor.pdfLinks"),children:E.filter(t=>t.subtype==="Link"&&Array.isArray(t.rect)&&t.rect.length>=4).map((t,p)=>{const[f,L]=u.convertToViewportPoint(t.rect[0],t.rect[1]),[b,x]=u.convertToViewportPoint(t.rect[2],t.rect[3]),o=[f,L,b,x],m=Math.min(o[0],o[2]),P=Math.min(o[1],o[3]),y=Math.abs(o[2]-o[0]),z=Math.abs(o[3]-o[1]),v=typeof t.title=="string"?t.title.trim():"",S=typeof t.unsafeUrl=="string"?t.unsafeUrl.trim():"",g=(typeof t.url=="string"?t.url.trim():"")||(/^(?:https?|mailto|ftp):/i.test(S)?S:""),I=t.dest;if(!g&&I==null)return null;const w=!!g;return n.jsx("a",{className:"pdf-annotation-link",href:g||"#",target:w?"_blank":void 0,rel:w?"noreferrer":void 0,title:v||(w?g:h),"aria-label":v||(w?g:h),style:{left:m,top:P,width:y,height:z},onClick:w?void 0:e=>{e.preventDefault(),N(I)}},`${p}-${m}-${P}`)})})}export{te as PdfPreview};
@@ -1 +0,0 @@
1
- import{B as r}from"./BinaryModule-Aj1vLnwf-C2TCdTPg.js";import"./index-CI87C5Wy.js";import"./react-CsO_xvbl.js";const a=""+new URL("/assets/harper_wasm_bg-LWXDb7Ll.wasm",import.meta.url).href,o=r.create(a,"full");export{o as binary};