@jacktea/pdf-viewer-server 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var _=require("socket.io"),E=require("http"),p={port:process.env.PORT?parseInt(process.env.PORT,10):3e3,corsOrigins:process.env.CORS_ORIGINS||"http://localhost:5173",allowAllOrigins:process.env.ALLOW_ALL_ORIGINS==="true",roomCleanupTimeout:parseInt(process.env.ROOM_CLEANUP_TIMEOUT||"300000",10),requireAuth:process.env.REQUIRE_AUTH==="true",defaultRole:j(process.env.DEFAULT_USER_ROLE),authTokenRoles:F(process.env.COLLAB_AUTH_TOKENS||"")};function N(e){if(e==="admin"||e==="user"||e==="viewer")return e}function j(e){return N(e)==="viewer"?"viewer":"user"}function F(e){let t=e.split(",").map(o=>o.trim()).filter(Boolean),n=new Map;for(let o of t){let s=o.indexOf(":");if(s<=0)continue;let i=o.slice(0,s).trim(),d=N(o.slice(s+1).trim());!i||!d||n.set(i,d)}return n}function M(e){let t=e?p.authTokenRoles.get(e):void 0;return p.requireAuth&&!t?null:t??p.defaultRole}function O(e){if(typeof e=="string"&&e.trim())return e;if(Array.isArray(e)){for(let t of e)if(typeof t=="string"&&t.trim())return t}}function x(e){let t=O(e.handshake.query.authToken);return t||O(e.handshake.auth?.authToken)}function L(){if(p.allowAllOrigins)return console.warn("[Security Warning] ALLOW_ALL_ORIGINS is enabled. Do not use in production!"),"*";let e=p.corsOrigins.split(",").map(t=>t.trim()).filter(Boolean);return e.length===1?e[0]:e}var q=(0,E.createServer)(),w=new _.Server(q,{cors:{origin:L(),methods:["GET","POST"],credentials:!0},pingInterval:25e3,pingTimeout:2e4}),I=new Map,C=new Map;function H(e){let t=I.get(e);return t||(t={annotations:[],comments:[],users:[],mode:"normal",createdAt:Date.now(),lastActivityAt:Date.now()},I.set(e,t)),t}function D(e){let t=I.get(e);t&&(t.lastActivityAt=Date.now())}function V(e){let t=C.get(e);t&&clearTimeout(t);let n=setTimeout(()=>{let o=I.get(e);o&&o.users.length===0&&(I.delete(e),C.delete(e),console.log(`[${new Date().toISOString()}] Room ${e} cleaned up after inactivity`))},p.roomCleanupTimeout);C.set(e,n)}function B(e){let t=C.get(e);t&&(clearTimeout(t),C.delete(e))}var G={rules:[{effect:"allow",actions:"*",roles:["admin"]},{effect:"allow",actions:["annotation.view","comment.view"],roles:["viewer"]},{effect:"allow",actions:["annotation.view","annotation.create","annotation.comment.create","comment.view"],roles:["user"]},{effect:"allow",actions:["annotation.update","annotation.delete"],roles:["user"],owner:!0},{effect:"allow",actions:["comment.update","comment.delete","comment.reply","thread.resolve","thread.reopen"],roles:["user"],owner:!0}]};function W(e){return e==="annotation.view"||e==="comment.view"}function J(e,t){return e.actions==="*"?!0:e.actions.includes(t)}function K(e,t,n){let o=Array.isArray(e.roles)&&e.roles.length>0,s=Array.isArray(e.users)&&e.users.length>0,i=!o||e.roles.some(l=>n.includes(l)),d=!s||e.users.includes(t);return i&&d}function Q(e,t,n){return e.owner?!!(t&&t===n):!0}function $(e,t,n,o,s){if(!e||e.length===0)return;let i=!1;for(let d of e)if(J(d,t)&&K(d,n,o)&&Q(d,s,n)){if(d.effect==="deny")return"deny";i=!0}return i?"allow":void 0}function h(e){let t=[e.userRole??"user"];if(e.previewMode&&!W(e.action))return{allowed:!1,reason:"preview_mode"};let n=$(e.resourcePolicy?.rules,e.action,e.userId,t,e.ownerId);if(n==="deny")return{allowed:!1,reason:"resource_explicit_deny"};if(n==="allow")return{allowed:!0};let o=$(G.rules,e.action,e.userId,t,e.ownerId);return o==="deny"?{allowed:!1,reason:"global_explicit_deny"}:o==="allow"?{allowed:!0}:{allowed:!1,reason:"no_matching_allow"}}function Y(e,t,n,o){return{view:h({action:"annotation.view",userId:t,userRole:n,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,update:h({action:"annotation.update",userId:t,userRole:n,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,delete:h({action:"annotation.delete",userId:t,userRole:n,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,commentCreate:h({action:"annotation.comment.create",userId:t,userRole:n,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,resolveThread:h({action:"thread.resolve",userId:t,userRole:n,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,reopenThread:h({action:"thread.reopen",userId:t,userRole:n,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed}}function X(e,t,n,o,s){let i=h({action:"comment.reply",userId:n,userRole:o,ownerId:e.authorId,resourcePolicy:e.acl,previewMode:s}).allowed;return{view:h({action:"comment.view",userId:n,userRole:o,ownerId:e.authorId,resourcePolicy:e.acl,previewMode:s}).allowed,update:h({action:"comment.update",userId:n,userRole:o,ownerId:e.authorId,resourcePolicy:e.acl,previewMode:s}).allowed,delete:h({action:"comment.delete",userId:n,userRole:o,ownerId:e.authorId,resourcePolicy:e.acl,previewMode:s}).allowed,reply:i}}function Z(e,t,n,o,s){return{resolve:h({action:"thread.resolve",userId:n,userRole:o,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:s}).allowed,reopen:h({action:"thread.reopen",userId:n,userRole:o,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:s}).allowed}}function U(e){let{capabilities:t,...n}=e;return{...n,metadata:{...n.metadata}}}function R(e){let{capabilities:t,...n}=e;return{...n}}function P(e,t,n){let o=[];for(let d of e.annotations){let l=Y(d,t,n,e.mode==="preview");l.view&&o.push({...d,capabilities:l})}let s=new Map(o.map(d=>[d.id,d])),i=[];for(let d of e.comments){let l=s.get(d.targetAnnotationId);if(!l)continue;let f=[];for(let u of d.comments){let v=X(u,l,t,n,e.mode==="preview");v.view&&f.push({...u,capabilities:v})}i.push({...d,comments:f,capabilities:Z(l,d,t,n,e.mode==="preview")})}return{annotations:o,comments:i,activeUsers:e.users,documentMode:e.mode}}function ee(e){return e==="admin"}function te(e){if(!e||typeof e!="object")return!1;let t=e,n=["add","update","remove"];return typeof t.type=="string"&&n.includes(t.type)}function ne(e){if(!e||typeof e!="object")return!1;let t=e,n=["add","update","remove"];return typeof t.type=="string"&&n.includes(t.type)}function k(e){if(typeof e=="number"&&Number.isFinite(e))return e;if(typeof e=="string"){let t=e.trim();if(!t)return;let n=Number(t);if(Number.isFinite(n))return n;let o=Date.parse(t);if(!Number.isNaN(o))return o}}w.use((e,t)=>{if(!p.requireAuth){t();return}let n=x(e);if(!M(n)){console.warn(`[${new Date().toISOString()}] Unauthorized handshake: socket=${e.id}, ip=${e.handshake.address}`),t(new Error("Unauthorized collaboration connection"));return}t()});w.on("connection",e=>{console.log(`[${new Date().toISOString()}] New connection: ${e.id}`),e.on("error",t=>{console.error(`[${new Date().toISOString()}] Socket error for ${e.id}:`,t.message)}),e.on("join-document",({documentId:t,user:n,token:o})=>{if(!t||typeof t!="string"){e.emit("error",{message:"Invalid document ID"});return}if(!n||typeof n.id!="string"){e.emit("error",{message:"Invalid user information"});return}let s=typeof o=="string"?o:void 0,i=x(e),d=p.requireAuth?i:s||i,l=M(d);if(!l){e.emit("error",{message:"Unauthorized collaboration connection"}),console.warn(`[${new Date().toISOString()}] Unauthorized join attempt: socket=${e.id}, document=${t}`),e.disconnect(!0);return}e.join(t),B(t);let f=H(t),u=f.users.find(c=>c.id===n.id);u?(u.name=n.name??u.name,u.color=n.color??u.color,u.avatar=n.avatar??u.avatar,u.role=l):f.users.push({id:n.id,name:n.name,color:n.color,avatar:n.avatar,role:l}),e.documentId=t,e.userId=n.id,e.userRole=l;let v=w.sockets.adapter.rooms.get(t)?.size||0;console.log(`[${new Date().toISOString()}] User ${n.name} (${n.id}) joined document ${t} as ${l}. Unique users: ${f.users.length}, Connections: ${v}`),e.emit("sync",P(f,n.id,l)),w.to(t).emit("users:update",f.users),D(t)}),e.on("client:change",t=>{let{documentId:n,userId:o,userRole:s}=e;if(!n||!o)return;let i=I.get(n);if(!i)return;if(!t||typeof t!="object"||!t.type){console.warn(`[${new Date().toISOString()}] Invalid change payload from ${e.id}`);return}let d=i.mode==="preview",l=(c,a)=>{let r=a==="preview_mode"?"PERMISSION_DENIED_PREVIEW_MODE":"PERMISSION_DENIED";e.emit("error",{message:r,action:c,reason:a})},f=(c,a={})=>{let r=h({action:c,userId:o,userRole:s,ownerId:a.ownerId,resourcePolicy:a.resourcePolicy,previewMode:d});return r.allowed||l(c,r.reason),r.allowed},u=null;if(t.type==="annotation"){let{change:c}=t;if(!te(c)){console.warn(`[${new Date().toISOString()}] Invalid annotation change from ${e.id}`);return}if(c.type==="add"&&c.annotation){let a=U(c.annotation);if(!f("annotation.create"))return;let r={...a,metadata:{...a.metadata,authorId:o,createdAt:k(a.metadata?.createdAt)??Date.now()}};i.annotations.push(r),u={type:"annotation",change:{type:"add",annotation:r}}}else if(c.type==="update"&&c.annotation){let a=U(c.annotation),r=i.annotations.find(A=>A.id===a.id);if(!r)return;let m=r.metadata.status==="resolved"?"resolved":"open",g=a.metadata.status==="resolved"?"resolved":a.metadata.status==="open"?"open":m,y=g!==m,b=A=>{let{status:oe,updatedAt:ae,...z}=A.metadata;return{...A,metadata:z}};if(!(JSON.stringify(b(r))===JSON.stringify(b(a)))&&!f("annotation.update",{ownerId:r.metadata.authorId,resourcePolicy:r.metadata.acl})||y&&!f(g==="resolved"?"thread.resolve":"thread.reopen",{ownerId:r.metadata.authorId,resourcePolicy:r.metadata.acl}))return;let T=i.annotations.findIndex(A=>A.id===a.id);if(T!==-1){let A={...a,metadata:{...a.metadata,status:g,updatedAt:Date.now()}};i.annotations[T]=A,u={type:"annotation",change:{type:"update",annotation:A}}}}else if(c.type==="remove"&&c.annotation){let a=i.annotations.find(r=>r.id===c.annotation?.id);if(!a||!f("annotation.delete",{ownerId:a.metadata.authorId,resourcePolicy:a.metadata.acl}))return;i.annotations=i.annotations.filter(r=>r.id!==c.annotation?.id),u={type:"annotation",change:{type:"remove",annotation:a}}}}else if(t.type==="comment"){let{change:c}=t;if(!ne(c)){console.warn(`[${new Date().toISOString()}] Invalid comment change from ${e.id}`);return}if(c.type==="add"&&c.comment){let a=R(c.comment),r=i.annotations.find(y=>y.id===a.targetAnnotationId);if(!r)return;if(a.parentId){let b=i.comments.find(S=>S.targetAnnotationId===a.targetAnnotationId)?.comments.find(S=>S.id===a.parentId);if(!b||!f("comment.reply",{ownerId:b.authorId,resourcePolicy:b.acl}))return}else if(!f("annotation.comment.create",{ownerId:r.metadata.authorId,resourcePolicy:r.metadata.acl}))return;let m=i.comments.find(y=>y.targetAnnotationId===a.targetAnnotationId);m||(m={id:a.targetAnnotationId,targetAnnotationId:a.targetAnnotationId,comments:[]},i.comments.push(m));let g={...a,authorId:o,createdAt:k(a.createdAt)??Date.now()};m.comments.push(g),u={type:"comment",change:{type:"add",comment:g}}}else if(c.type==="update"&&c.comment){let a=R(c.comment),r=i.comments.find(m=>m.targetAnnotationId===a.targetAnnotationId);if(r){let m=r.comments.find(g=>g.id===a.id);if(m){if(!f("comment.update",{ownerId:m.authorId,resourcePolicy:m.acl}))return;let g=r.comments.findIndex(y=>y.id===a.id);if(g!==-1){let y={...a,updatedAt:Date.now()};r.comments[g]=y,u={type:"comment",change:{type:"update",comment:y}}}}}}else if(c.type==="remove"&&c.comment){let a=R(c.comment),r=i.comments.find(m=>m.targetAnnotationId===a.targetAnnotationId);if(r){let m=r.comments.find(g=>g.id===a.id);if(m){if(!f("comment.delete",{ownerId:m.authorId,resourcePolicy:m.acl}))return;r.comments=r.comments.filter(g=>g.id!==a.id),u={type:"comment",change:{type:"remove",comment:m}},r.comments.length===0&&(i.comments=i.comments.filter(g=>g.id!==r.id))}}}}u&&e.broadcast.to(n).emit("remote:change",u);let v=w.sockets.adapter.rooms.get(n);if(v)for(let c of v){let a=w.sockets.sockets.get(c);a?.userId&&a.emit("sync",P(i,a.userId,a.userRole))}D(n)}),e.on("document:set-mode",({mode:t})=>{let{documentId:n,userRole:o}=e;if(!n)return;let s=I.get(n);if(!s||t!=="normal"&&t!=="preview")return;if(!ee(o)){e.emit("error",{message:"PERMISSION_DENIED",action:"document.setMode"});return}if(s.mode===t)return;s.mode=t,w.to(n).emit("document:mode:changed",t);let i=w.sockets.adapter.rooms.get(n);if(i)for(let d of i){let l=w.sockets.sockets.get(d);l?.userId&&l.emit("sync",P(s,l.userId,l.userRole))}}),e.on("client:interaction",t=>{let{documentId:n,userId:o}=e;if(!n||!o||!I.has(n)||!t||typeof t!="object"||!t.type)return;let s={...t,userId:o};e.broadcast.to(n).emit("remote:interaction",s)}),e.on("disconnect",()=>{let{documentId:t,userId:n}=e;if(t&&I.has(t)){let o=I.get(t),s=w.sockets.adapter.rooms.get(t),i=!1;if(s)for(let l of s){if(l===e.id)continue;if(w.sockets.sockets.get(l)?.userId===n){i=!0;break}}i||(o.users=o.users.filter(l=>l.id!==n),w.to(t).emit("users:update",o.users));let d=(s?.size||1)-1;console.log(`[${new Date().toISOString()}] User ${n} disconnected from ${t}. Remaining connections: ${d}, Unique users: ${o.users.length}`),o.users.length===0&&(console.log(`[${new Date().toISOString()}] Room ${t} is now empty, scheduling cleanup`),V(t))}})});q.listen(p.port,()=>{console.log(`[${new Date().toISOString()}] Collaboration server running on port ${p.port}`),console.log(`[${new Date().toISOString()}] CORS origins: ${L()}`),console.log(`[${new Date().toISOString()}] Heartbeat: pingInterval=25s, pingTimeout=20s`),console.log(`[${new Date().toISOString()}] Room cleanup timeout: ${p.roomCleanupTimeout}ms`),console.log(`[${new Date().toISOString()}] Auth mode: requireAuth=${p.requireAuth}, tokenRoles=${p.authTokenRoles.size}, defaultRole=${p.defaultRole}`)});
|
|
2
|
+
"use strict";var N=require("socket.io"),M=require("http");function D(e){return String(e)}function f(e,n){return e==null||n==null?e==null&&n==null:D(e)===D(n)}var h={port:process.env.PORT?parseInt(process.env.PORT,10):3e3,corsOrigins:process.env.CORS_ORIGINS||"http://localhost:5173",allowAllOrigins:process.env.ALLOW_ALL_ORIGINS==="true",roomCleanupTimeout:parseInt(process.env.ROOM_CLEANUP_TIMEOUT||"300000",10),requireAuth:process.env.REQUIRE_AUTH==="true",defaultRole:H(process.env.DEFAULT_USER_ROLE),authTokenRoles:V(process.env.COLLAB_AUTH_TOKENS||"")};function x(e){if(e==="admin"||e==="user"||e==="viewer")return e}function H(e){return x(e)==="viewer"?"viewer":"user"}function V(e){let n=e.split(",").map(o=>o.trim()).filter(Boolean),t=new Map;for(let o of n){let s=o.indexOf(":");if(s<=0)continue;let i=o.slice(0,s).trim(),l=x(o.slice(s+1).trim());!i||!l||t.set(i,l)}return t}function L(e){let n=e?h.authTokenRoles.get(e):void 0;return h.requireAuth&&!n?null:n??h.defaultRole}function $(e){if(typeof e=="string"&&e.trim())return e;if(Array.isArray(e)){for(let n of e)if(typeof n=="string"&&n.trim())return n}}function q(e){let n=$(e.handshake.query.authToken);return n||$(e.handshake.auth?.authToken)}function z(){if(h.allowAllOrigins)return console.warn("[Security Warning] ALLOW_ALL_ORIGINS is enabled. Do not use in production!"),"*";let e=h.corsOrigins.split(",").map(n=>n.trim()).filter(Boolean);return e.length===1?e[0]:e}var j=(0,M.createServer)(),y=new N.Server(j,{cors:{origin:z(),methods:["GET","POST"],credentials:!0},pingInterval:25e3,pingTimeout:2e4}),A=new Map,S=new Map;function B(e){let n=A.get(e);return n||(n={annotations:[],comments:[],users:[],mode:"normal",createdAt:Date.now(),lastActivityAt:Date.now()},A.set(e,n)),n}function U(e){let n=A.get(e);n&&(n.lastActivityAt=Date.now())}function G(e){let n=S.get(e);n&&clearTimeout(n);let t=setTimeout(()=>{let o=A.get(e);o&&o.users.length===0&&(A.delete(e),S.delete(e),console.log(`[${new Date().toISOString()}] Room ${e} cleaned up after inactivity`))},h.roomCleanupTimeout);S.set(e,t)}function W(e){let n=S.get(e);n&&(clearTimeout(n),S.delete(e))}var J={rules:[{effect:"allow",actions:"*",roles:["admin"]},{effect:"allow",actions:["annotation.view","comment.view"],roles:["viewer"]},{effect:"allow",actions:["annotation.view","annotation.create","annotation.comment.create","comment.view"],roles:["user"]},{effect:"allow",actions:["annotation.update","annotation.delete"],roles:["user"],owner:!0},{effect:"allow",actions:["comment.update","comment.delete","comment.reply","thread.resolve","thread.reopen"],roles:["user"],owner:!0}]};function K(e){return e==="annotation.view"||e==="comment.view"}function Q(e,n){return e.actions==="*"?!0:e.actions.includes(n)}function Y(e,n,t){let o=Array.isArray(e.roles)&&e.roles.length>0,s=Array.isArray(e.users)&&e.users.length>0,i=!o||e.roles.some(d=>t.includes(d)),l=!s||e.users.some(d=>f(d,n));return i&&l}function X(e,n,t){return e.owner?!!(n!=null&&f(n,t)):!0}function k(e,n,t,o,s){if(!e||e.length===0)return;let i=!1;for(let l of e)if(Q(l,n)&&Y(l,t,o)&&X(l,s,t)){if(l.effect==="deny")return"deny";i=!0}return i?"allow":void 0}function I(e){let n=[e.userRole??"user"];if(e.previewMode&&!K(e.action))return{allowed:!1,reason:"preview_mode"};let t=k(e.resourcePolicy?.rules,e.action,e.userId,n,e.ownerId);if(t==="deny")return{allowed:!1,reason:"resource_explicit_deny"};if(t==="allow")return{allowed:!0};let o=k(J.rules,e.action,e.userId,n,e.ownerId);return o==="deny"?{allowed:!1,reason:"global_explicit_deny"}:o==="allow"?{allowed:!0}:{allowed:!1,reason:"no_matching_allow"}}function Z(e,n,t,o){return{view:I({action:"annotation.view",userId:n,userRole:t,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,update:I({action:"annotation.update",userId:n,userRole:t,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,delete:I({action:"annotation.delete",userId:n,userRole:t,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,commentCreate:I({action:"annotation.comment.create",userId:n,userRole:t,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,resolveThread:I({action:"thread.resolve",userId:n,userRole:t,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed,reopenThread:I({action:"thread.reopen",userId:n,userRole:t,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:o}).allowed}}function ee(e,n,t,o,s){let i=I({action:"comment.reply",userId:t,userRole:o,ownerId:e.authorId,resourcePolicy:e.acl,previewMode:s}).allowed;return{view:I({action:"comment.view",userId:t,userRole:o,ownerId:e.authorId,resourcePolicy:e.acl,previewMode:s}).allowed,update:I({action:"comment.update",userId:t,userRole:o,ownerId:e.authorId,resourcePolicy:e.acl,previewMode:s}).allowed,delete:I({action:"comment.delete",userId:t,userRole:o,ownerId:e.authorId,resourcePolicy:e.acl,previewMode:s}).allowed,reply:i}}function ne(e,n,t,o,s){return{resolve:I({action:"thread.resolve",userId:t,userRole:o,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:s}).allowed,reopen:I({action:"thread.reopen",userId:t,userRole:o,ownerId:e.metadata.authorId,resourcePolicy:e.metadata.acl,previewMode:s}).allowed}}function _(e){let{capabilities:n,...t}=e;return{...t,metadata:{...t.metadata}}}function P(e){let{capabilities:n,...t}=e;return{...t}}function T(e,n,t){let o=[];for(let l of e.annotations){let d=Z(l,n,t,e.mode==="preview");d.view&&o.push({...l,capabilities:d})}let s=new Map(o.map(l=>[D(l.id),l])),i=[];for(let l of e.comments){let d=s.get(D(l.targetAnnotationId));if(!d)continue;let p=[];for(let u of l.comments){let v=ee(u,d,n,t,e.mode==="preview");v.view&&p.push({...u,capabilities:v})}i.push({...l,comments:p,capabilities:ne(d,l,n,t,e.mode==="preview")})}return{annotations:o,comments:i,activeUsers:e.users,documentMode:e.mode}}function te(e){return e==="admin"}function oe(e){if(!e||typeof e!="object")return!1;let n=e,t=["add","update","remove"];return typeof n.type=="string"&&t.includes(n.type)}function ae(e){if(!e||typeof e!="object")return!1;let n=e,t=["add","update","remove"];return typeof n.type=="string"&&t.includes(n.type)}function E(e){if(typeof e=="number"&&Number.isFinite(e))return e;if(typeof e=="string"){let n=e.trim();if(!n)return;let t=Number(n);if(Number.isFinite(t))return t;let o=Date.parse(n);if(!Number.isNaN(o))return o}}y.use((e,n)=>{if(!h.requireAuth){n();return}let t=q(e);if(!L(t)){console.warn(`[${new Date().toISOString()}] Unauthorized handshake: socket=${e.id}, ip=${e.handshake.address}`),n(new Error("Unauthorized collaboration connection"));return}n()});y.on("connection",e=>{console.log(`[${new Date().toISOString()}] New connection: ${e.id}`),e.on("error",n=>{console.error(`[${new Date().toISOString()}] Socket error for ${e.id}:`,n.message)}),e.on("join-document",({documentId:n,user:t,token:o})=>{if(!n||typeof n!="string"){e.emit("error",{message:"Invalid document ID"});return}if(!t||typeof t.id!="string"&&typeof t.id!="number"){e.emit("error",{message:"Invalid user information"});return}let s=typeof o=="string"?o:void 0,i=q(e),l=h.requireAuth?i:s||i,d=L(l);if(!d){e.emit("error",{message:"Unauthorized collaboration connection"}),console.warn(`[${new Date().toISOString()}] Unauthorized join attempt: socket=${e.id}, document=${n}`),e.disconnect(!0);return}e.join(n),W(n);let p=B(n),u=p.users.find(c=>f(c.id,t.id));u?(u.name=t.name??u.name,u.color=t.color??u.color,u.avatar=t.avatar??u.avatar,u.role=d):p.users.push({id:t.id,name:t.name,color:t.color,avatar:t.avatar,role:d}),e.documentId=n,e.userId=t.id,e.userRole=d;let v=y.sockets.adapter.rooms.get(n)?.size||0;console.log(`[${new Date().toISOString()}] User ${t.name} (${t.id}) joined document ${n} as ${d}. Unique users: ${p.users.length}, Connections: ${v}`),e.emit("sync",T(p,t.id,d)),y.to(n).emit("users:update",p.users),U(n)}),e.on("client:change",n=>{let{documentId:t,userId:o,userRole:s}=e;if(!t||!o)return;let i=A.get(t);if(!i)return;if(!n||typeof n!="object"||!n.type){console.warn(`[${new Date().toISOString()}] Invalid change payload from ${e.id}`);return}let l=i.mode==="preview",d=(c,a)=>{let r=a==="preview_mode"?"PERMISSION_DENIED_PREVIEW_MODE":"PERMISSION_DENIED";e.emit("error",{message:r,action:c,reason:a})},p=(c,a={})=>{let r=I({action:c,userId:o,userRole:s,ownerId:a.ownerId,resourcePolicy:a.resourcePolicy,previewMode:l});return r.allowed||d(c,r.reason),r.allowed},u=null;if(n.type==="annotation"){let{change:c}=n;if(!oe(c)){console.warn(`[${new Date().toISOString()}] Invalid annotation change from ${e.id}`);return}if(c.type==="add"&&c.annotation){let a=_(c.annotation);if(!p("annotation.create"))return;let r={...a,metadata:{...a.metadata,authorId:o,createdAt:E(a.metadata?.createdAt)??Date.now()}};i.annotations.push(r),u={type:"annotation",change:{type:"add",annotation:r}}}else if(c.type==="update"&&c.annotation){let a=_(c.annotation),r=i.annotations.find(b=>f(b.id,a.id));if(!r)return;let m=r.metadata.status==="resolved"?"resolved":"open",g=a.metadata.status==="resolved"?"resolved":a.metadata.status==="open"?"open":m,w=g!==m,C=b=>{let{status:ie,updatedAt:re,...F}=b.metadata;return{...b,metadata:F}};if(!(JSON.stringify(C(r))===JSON.stringify(C(a)))&&!p("annotation.update",{ownerId:r.metadata.authorId,resourcePolicy:r.metadata.acl})||w&&!p(g==="resolved"?"thread.resolve":"thread.reopen",{ownerId:r.metadata.authorId,resourcePolicy:r.metadata.acl}))return;let O=i.annotations.findIndex(b=>f(b.id,a.id));if(O!==-1){let b={...a,metadata:{...a.metadata,status:g,updatedAt:Date.now()}};i.annotations[O]=b,u={type:"annotation",change:{type:"update",annotation:b}}}}else if(c.type==="remove"&&c.annotation){let a=i.annotations.find(r=>f(r.id,c.annotation?.id));if(!a||!p("annotation.delete",{ownerId:a.metadata.authorId,resourcePolicy:a.metadata.acl}))return;i.annotations=i.annotations.filter(r=>!f(r.id,c.annotation?.id)),u={type:"annotation",change:{type:"remove",annotation:a}}}}else if(n.type==="comment"){let{change:c}=n;if(!ae(c)){console.warn(`[${new Date().toISOString()}] Invalid comment change from ${e.id}`);return}if(c.type==="add"&&c.comment){let a=P(c.comment),r=i.annotations.find(w=>f(w.id,a.targetAnnotationId));if(!r)return;if(a.parentId){let C=i.comments.find(R=>f(R.targetAnnotationId,a.targetAnnotationId))?.comments.find(R=>f(R.id,a.parentId));if(!C||!p("comment.reply",{ownerId:C.authorId,resourcePolicy:C.acl}))return}else if(!p("annotation.comment.create",{ownerId:r.metadata.authorId,resourcePolicy:r.metadata.acl}))return;let m=i.comments.find(w=>f(w.targetAnnotationId,a.targetAnnotationId));m||(m={id:a.targetAnnotationId,targetAnnotationId:a.targetAnnotationId,comments:[]},i.comments.push(m));let g={...a,authorId:o,createdAt:E(a.createdAt)??Date.now()};m.comments.push(g),u={type:"comment",change:{type:"add",comment:g}}}else if(c.type==="update"&&c.comment){let a=P(c.comment),r=i.comments.find(m=>f(m.targetAnnotationId,a.targetAnnotationId));if(r){let m=r.comments.find(g=>f(g.id,a.id));if(m){if(!p("comment.update",{ownerId:m.authorId,resourcePolicy:m.acl}))return;let g=r.comments.findIndex(w=>f(w.id,a.id));if(g!==-1){let w={...a,updatedAt:Date.now()};r.comments[g]=w,u={type:"comment",change:{type:"update",comment:w}}}}}}else if(c.type==="remove"&&c.comment){let a=P(c.comment),r=i.comments.find(m=>f(m.targetAnnotationId,a.targetAnnotationId));if(r){let m=r.comments.find(g=>f(g.id,a.id));if(m){if(!p("comment.delete",{ownerId:m.authorId,resourcePolicy:m.acl}))return;r.comments=r.comments.filter(g=>!f(g.id,a.id)),u={type:"comment",change:{type:"remove",comment:m}},r.comments.length===0&&(i.comments=i.comments.filter(g=>!f(g.id,r.id)))}}}}u&&e.broadcast.to(t).emit("remote:change",u);let v=y.sockets.adapter.rooms.get(t);if(v)for(let c of v){let a=y.sockets.sockets.get(c);a?.userId!=null&&a.emit("sync",T(i,a.userId,a.userRole))}U(t)}),e.on("document:set-mode",({mode:n})=>{let{documentId:t,userRole:o}=e;if(!t)return;let s=A.get(t);if(!s||n!=="normal"&&n!=="preview")return;if(!te(o)){e.emit("error",{message:"PERMISSION_DENIED",action:"document.setMode"});return}if(s.mode===n)return;s.mode=n,y.to(t).emit("document:mode:changed",n);let i=y.sockets.adapter.rooms.get(t);if(i)for(let l of i){let d=y.sockets.sockets.get(l);d?.userId!=null&&d.emit("sync",T(s,d.userId,d.userRole))}}),e.on("client:interaction",n=>{let{documentId:t,userId:o}=e;if(!t||o==null||!A.has(t)||!n||typeof n!="object"||!n.type)return;let s={...n,userId:o};e.broadcast.to(t).emit("remote:interaction",s)}),e.on("disconnect",()=>{let{documentId:n,userId:t}=e;if(n&&A.has(n)){let o=A.get(n),s=y.sockets.adapter.rooms.get(n),i=!1;if(s)for(let d of s){if(d===e.id)continue;let p=y.sockets.sockets.get(d);if(f(p?.userId,t)){i=!0;break}}i||(o.users=o.users.filter(d=>!f(d.id,t)),y.to(n).emit("users:update",o.users));let l=(s?.size||1)-1;console.log(`[${new Date().toISOString()}] User ${t} disconnected from ${n}. Remaining connections: ${l}, Unique users: ${o.users.length}`),o.users.length===0&&(console.log(`[${new Date().toISOString()}] Room ${n} is now empty, scheduling cleanup`),G(n))}})});j.listen(h.port,()=>{console.log(`[${new Date().toISOString()}] Collaboration server running on port ${h.port}`),console.log(`[${new Date().toISOString()}] CORS origins: ${z()}`),console.log(`[${new Date().toISOString()}] Heartbeat: pingInterval=25s, pingTimeout=20s`),console.log(`[${new Date().toISOString()}] Room cleanup timeout: ${h.roomCleanupTimeout}ms`),console.log(`[${new Date().toISOString()}] Auth mode: requireAuth=${h.requireAuth}, tokenRoles=${h.authTokenRoles.size}, defaultRole=${h.defaultRole}`)});
|