Initial
This commit is contained in:
1
resources/app/dist/components/activity.mjs
vendored
Normal file
1
resources/app/dist/components/activity.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default class Activity{constructor(t){this.#t=t,this.#e=setInterval((()=>this.heartbeat(this)),15e3),this.#i()}#t;#e;users={};worldTime;#s;#i(){this.#s=Date.now(),db.Setting.getValue("core.time").then((t=>{if(this.worldTime=Number.isNumeric(t)?Number(t):0,void 0===t)return db.Setting.set("core.time",0)}))}get serverTime(){return Date.now()-this.#s}heartbeat(){if(game.world===this.#t){if(game.users)for(let t of game.users)t.id in this.users&&0===t.sockets.length&&this.deactivateUser(t)}else clearInterval(this.#e)}activateUser(t){const{express:e,game:i,db:s}=global;return"string"==typeof t&&(t=i.users.find((e=>e.id===t))),t instanceof s.User&&(!(t.id in this.users)&&(this.users[t.id]={},e.io.emit("userActivity",t.id,{active:!0}),!0))}deactivateUser(t){const{express:e,db:i}=global;if(!(t instanceof i.User))throw new Error("You must provide a User document instance to the deactivateUser method");return!t.sockets.length&&(t.id in this.users&&(delete this.users[t.id],e.io.emit("userActivity",t.id,{active:!1})),!0)}static socketListeners(t){this._onActivate(t,!0),t.on("disconnect",(()=>this._onActivate(t,!1))),t.on("userActivity",((e,i)=>Activity.#a(t,e,i))),t.on("getUserActivity",(()=>Activity.#r(t))),t.on("pause",(e=>this.pause(t,e))),t.on("time",this._onGameTime),t.on("reload",(()=>{t.user.can("SETTINGS_MODIFY")&&t.broadcast.emit("reload")}))}static _onActivate(t,e){const{game:i}=global;i.ready&&i.activity&&t.user&&(e?i.activity.activateUser(t.user):i.activity.deactivateUser(t.user))}static async _onGameTime(t){const{game:e}=global;return e.ready&&e.activity?t({serverTime:e.activity.serverTime,worldTime:e.activity.worldTime}):t({})}static pause(t,e){game.ready&&(global.logger.info(`Toggling game pause status to ${e}`),game.paused=e,t.broadcast.emit("pause",e))}static#a(t,e,i={}){if(!game.ready||!game.activity)return;const s=game.activity.users;e in s||(s[e]={});const a=s[e];for(const t of["sceneId","cursor","ruler","targets","av"])t in i&&(a[t]=i[t]);t.broadcast.emit("userActivity",e,i)}static#r(t){if(game.ready&&game.activity)for(const[e,i]of Object.entries(game.activity.users))t.emit("userActivity",e,i)}}
|
||||
1
resources/app/dist/components/demo.mjs
vendored
Normal file
1
resources/app/dist/components/demo.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import path from"node:path";import Files from"../files/files.mjs";import{World}from"../packages/_module.mjs";export async function resetDemo(){const{game:e,options:o}=globalThis;if(!o.demoMode)return null;const r=o.demo;e.world&&await e.world.deactivate(null,{asAdmin:!0});const t=path.join(World.baseDir,r.worldName);await fs.promises.rm(t,{force:!0,recursive:!0}),await Files.extractArchive(r.sourceZip,t,{removeRoot:r.worldName}),World.resetPackages();const s=World.get(r.worldName);return s?(await s.setup(),setTimeout(resetDemo,1e3*r.resetSeconds),s):(global.logger.warn(`Could not load ${r.worldName}`),null)}
|
||||
1
resources/app/dist/components/document-cache.mjs
vendored
Normal file
1
resources/app/dist/components/document-cache.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default class DocumentCache{static#e={Scene:5};#t={Scene:new Map};#c(e){const t=this.#s(e),c=DocumentCache.#e[e];if(t.size<=c)return;const s=Array.from(t.values());for(s.sort(((e,t)=>t.lastAccessed-e.lastAccessed));t.size>c;){const e=s.pop();t.delete(e.value.id)}}#s(e){const t=this.#t[e];if(!t)throw new Error(`The document cache does not support ${e} documents.`);return t}addAll(e){const t=e[0]?.documentName,c=this.#s(t),s=performance.now();e.forEach((e=>c.set(e.id,{lastAccessed:s,value:e}))),this.#c(t)}delete(e){this.#s(e.documentName).delete(e.id)}get(e,t){return this.#t[e]?.get(t)?.value}set(e){this.#s(e.documentName).set(e.id,{lastAccessed:performance.now(),value:e}),this.#c(e.documentName)}}
|
||||
1
resources/app/dist/components/progress-emitter.mjs
vendored
Normal file
1
resources/app/dist/components/progress-emitter.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{mergeObject}from"../../common/utils/helpers.mjs";export default class ProgressEmitter{constructor(t,o,e,s={},i={}){this.#t=t,this.#o=o,this.#e=e,this.#s=s,this.#i=mergeObject({log:!0,decimalPlaces:0},i)}#t;#o=null;#e;#s;#i;#n;get operationString(){return this.#i.operationName?`${this.#i.operationName} | `:""}complete({event:t="progress",context:o={},log:e,step:s}={}){const{express:i,logger:n}=global,{log:r,onProgress:c}=this.#i,{STEPS:a}=CONST.SETUP_PACKAGE_PROGRESS;s??=a.COMPLETE;const g={...this.#s,...o,step:s,pct:100,action:this.#t};c instanceof Function&&c(g),r&&n.info(`${this.operationString}${e??`${s} - 100%`}`),i.io.emit(t,g)}emit(t,{event:o="progress",force:e=!1,log:s,context:i={}}={}){const{express:n,logger:r}=global,{log:c,decimalPlaces:a,onProgress:g}=this.#i,p=Math.min(Number((t/this.#e*100).toFixed(a)),100),l=p!==this.#n,h={...this.#s,...i,pct:p,hasChanged:l,action:this.#t,step:this.#o};if(g instanceof Function&&g(h),e||l){if(c){const t=s??`${this.#t} ${this.#o} progress`;r.info(`${this.operationString}${t} - ${p}%`)}n.io.emit(o,h),this.#n=p}}error(t,{event:o="progress",log:e,context:s={}}={}){const{express:i,logger:n}=global,{log:r,onProgress:c}=this.#i,{STEPS:a}=CONST.SETUP_PACKAGE_PROGRESS,g={...this.#s,...s,pct:100,action:this.#t,step:a.ERROR,error:t.message,stack:t.stack};c instanceof Function&&c(g),r&&n.error(e??t),i.io.emit(o,g)}nextStep(t,o,e={}){this.#o=t,this.#e=o,mergeObject(this.#s,e)}log(t){logger.info(`${this.operationString}${t}`)}}
|
||||
1
resources/app/dist/components/prosemirror.mjs
vendored
Normal file
1
resources/app/dist/components/prosemirror.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{EditorState}from"prosemirror-state";import{Step}from"prosemirror-transform";import{defaultSchema}from"../../common/prosemirror/_module.mjs";import{serializeHTMLString}from"../../common/prosemirror/util.mjs";import{fromUuid}from"../core/utils.mjs";import{getProperty,isEmpty,setProperty}from"../../common/utils/helpers.mjs";export default class ProseMirrorAuthority{constructor(t,r){this.#t=t,this.#r=r,this.#e()}#r;get doc(){return this.#r}#s=0;get version(){return this.#s}#o=0;get offset(){return this.#o}#t;#i;history=[];users=new Set;applySteps(t,r){r.forEach((r=>{r=Step.fromJSON(defaultSchema,r);try{this.#r=r.apply(this.#r).doc}catch(r){return void logger.warn(`Failed to apply collaborative editing step for user ${t}. Editors may be in an inconsistent state.`)}this.#s++,this.history.push({userId:t,step:r})})),this.#a()}broadcast(t,...r){const{game:e}=global;for(const s of this.users){const o=e.users.find((t=>t.id===s));o&&o.sockets.forEach((e=>e.emit(t,...r)))}}#a(){const t=this.history.length-ProseMirrorAuthority.#n;t<1||(this.history.splice(0,t),this.#o+=t)}async#e(){let t=await db.Setting.getValue("core.editorAutosaveSecs")||60;t=Math.clamp(t,30,300),this.#i=setInterval(this.#u.bind(this),1e3*t)}async#u(){const{logger:t}=global,r=serializeHTMLString(this.doc),[e,s]=this.#t.split("#"),o=await fromUuid(e);if(o||t.warn(`Tried to autosave contents of ProseMirror instance '${this.#t}' but could not retrieve the document.`),!game?.ready)return;if(!o.isEmbedded&&!o.constructor.ready)return;const i={};setProperty(i,s,r),o.constructor.sanitizeUserInput(i,{document:o});const a=o.updateSource(i);if(!isEmpty(getProperty(a,s))){t.debug(`Broadcasting autosave update for instance '${this.#t}'.`);for(const t of game.users)t.sockets.forEach((t=>t.emit("pm.autosave",this.#t,r)));return o.isEmbedded?o.parent.save():o.save()}}static#n=256;static#c=new Map;static async create(t,r){const e=EditorState.fromJSON({schema:defaultSchema},r).doc;return new ProseMirrorAuthority(t,e)}static async getOrCreate(t,r){const e=ProseMirrorAuthority.#c,s=e.get(t)??await this.create(t,r);return s?(e.has(t)||e.set(t,s),s):null}static socketListeners(t){t.on("pm.editDocument",ProseMirrorAuthority.#d.bind(this,t.userId)),t.on("pm.receiveSteps",ProseMirrorAuthority.#h.bind(this,t)),t.on("pm.endSession",ProseMirrorAuthority.#m.bind(this,t.userId)),t.on("disconnect",ProseMirrorAuthority.#p.bind(this,t.userId))}static async#d(t,r,e,s){const o=await this.getOrCreate(r,e);if(!o)return s({});logger.debug(`Starting edit session for user '${t}' and instance '${r}'.`),o.users.add(t);s({state:EditorState.create({doc:o.doc}),version:o.version,users:Array.from(o.users)}),o.broadcast("pm.usersEditing",r,Array.from(o.users))}static#m(t,r){const{game:e}=global,s=ProseMirrorAuthority.#c.get(r);if(!s)return;s.users.delete(t)&&logger.debug(`Retiring user '${t}' from editing session for instance '${r}'.`),e.ready&&s.users.size?s.broadcast("pm.usersEditing",r,Array.from(s.users)):(logger.debug(`Instance '${r}' has no more active editors, cleaning up.`),clearInterval(s.#i),ProseMirrorAuthority.#c.delete(r))}static#p(t){for(const r of ProseMirrorAuthority.#c.keys())ProseMirrorAuthority.#m(t,r)}static#h(t,r,e,s){const{logger:o}=global,i=ProseMirrorAuthority.#c.get(r);if(!i)return void o.error("Editing steps were received for an JournalEntryPage that is not in an editing state.");const a=i.version-e;a>ProseMirrorAuthority.#n?t.emit("pm.resync",r):0===a&&(i.applySteps(t.userId,s),i.broadcast("pm.newSteps",r,i.offset,i.history))}}
|
||||
1
resources/app/dist/core/auth.mjs
vendored
Normal file
1
resources/app/dist/core/auth.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import crypto from"node:crypto";const _S3KT="17c4f39053ac5a50d5797c665ad1f4e6";export function createPassword(t,r){return{hash:hashPassword(t,r=r||randomString(64)),salt:r}}export function getSalt(t){return"string"==typeof(t=t??globalThis.options?.passwordSalt)&&t.length?t:_S3KT}export function randomString(t=16){return crypto.randomBytes(t).toString("hex").slice(0,t)}export function hashPassword(t,r){return crypto.pbkdf2Sync(t,r,1e3,64,"sha512").toString("hex")}export function testPassword(t,r,o){const n=crypto.pbkdf2Sync(t,o,1e3,64,"sha512"),e=Buffer.from(r,"hex");return n.length===e.length&&crypto.timingSafeEqual(n,e)}
|
||||
1
resources/app/dist/core/config.mjs
vendored
Normal file
1
resources/app/dist/core/config.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/core/game.mjs
vendored
Normal file
1
resources/app/dist/core/game.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import path from"node:path";import DocumentCache from"../components/document-cache.mjs";export default class GameServer{constructor(){this.#e()}active=!1;activity;documentCache=new DocumentCache;documentTypes;featuredContent;model;news;release=globalThis.release;paused=!1;permissions;ready=!1;system=null;modules=null;users=[];world=null;get packs(){return globalThis.db.packs}static get newsPath(){return path.join(paths.logs,"news.json")}saveNews(e,t){if(!e)return logger.warn("No news or featured content received from software update check response.");Object.defineProperties(this,{news:{value:e,configurable:!0,enumerable:!1},featuredContent:{value:t,configurable:!0,enumerable:!1}}),fs.writeFileSync(GameServer.newsPath,JSON.stringify({news:e,featuredContent:t},null,2))}#e(){let e;if(fs.existsSync(GameServer.newsPath)){try{e=JSON.parse(fs.readFileSync(GameServer.newsPath,"utf-8"))}catch(e){return void console.error(e)}Object.defineProperties(this,{news:{value:e.news,configurable:!0,enumerable:!1},featuredContent:{value:e.featuredContent,configurable:!0,enumerable:!1}})}}}
|
||||
1
resources/app/dist/core/license.mjs
vendored
Normal file
1
resources/app/dist/core/license.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import crypto from"node:crypto";import fs from"node:fs";import path from"node:path";import{isNewerVersion}from"../../common/utils/helpers.mjs";import{fetchJsonWithTimeout}from"../../common/utils/http.mjs";import{WEBSITE_API_URL}from"../../common/constants.mjs";const PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuWBSnz/TfOKdEpEPj9Gf\n7kFS82sBd5mdcT6it9P/gd/wkFehG0cm5nB+gmpt4ZMAJLCnWzHwL4ih5pKoINy6\nzqOhw0bzk2PjifQI1bcclmdmP+t2oE/GJOZ1BM7HFgPW8kiWRpFxTXJh6ooB2wb9\nSx89ABLvRIfzpKmuEl9qBKk9w7X+3yPCiZ5FvggCxQLvSkvDMOPZDxVDV3yQvFkO\naDFmhyMFPNnInWXEOYXR31IR3kgnIWC6Hjtw0TlpUHzz/j6aHXWfQu5kwM89HjaF\niPzhDdYWQZKaSTnXt7oNkyfccW7YdIONDf0xJ+lNPOe9OO0HRjhdLiLVwSMr965D\n+PBOkwLYlDlOgEQRZIzi88tsNFOX31BInaG2F8yhsDkQ16FK3A+04pfEYZp1H+mC\nMTWi274Od4i1NYctOS2bNwb2U1dMtwt2ZZmgbHSMv1fm1R/9iAfLrSDjgTbmhquA\norVCmzl1mTG8o0xE9IX5psH3bDJxVis1IeUBeMd+Js0dsY9jIU0uN9D0wke8C7cf\nUK6XzZkC5ujQl92WAKeQOtxkG7e1x5cq4T1tkaH/U4HJWdcAsN0qohQ4vV73Akpm\nb7ZzUrMFv9BdIfUXgNZuQutkfXgfrAFznjI/H9R1M2uQBbQnk+I7+/wA49DJND4s\nu3WiTypPaz1INacKD9bplx0CAwEAAQ==\n-----END PUBLIC KEY-----";export default class License{constructor(e){this.data=this.constructor.get(),this.status=this.constructor.STATUSES.NONE,this.service=e}static EULA_VERSION="11.293";static LICENSE_SIGNATURE_URL=`${WEBSITE_API_URL}/_api/license/sign/`;static SOFTWARE_UPDATE_URL=`${WEBSITE_API_URL}/_api/license/check/`;static SOFTWARE_DOWNLOAD_URL=`${WEBSITE_API_URL}/_api/license/download/`;static LICENSE_API_KEY="foundryvtt_hkmg5t4zxc092e31mkfbg3";static STATUSES={NONE:0,INVALID:1,VALID:2,UNKNOWN:3};static PUBLIC_KEY=PUBLIC_KEY;get authorizationHeader(){return`APIKey:${this.service.key?`${this.service.id}_${this.service.key}`:this.constructor.LICENSE_API_KEY}`}static get path(){return path.join(global.paths.config,"license.json")}static get(){if(!fs.existsSync(this.path))return{};try{return JSON.parse(fs.readFileSync(this.path,"utf8"))}catch(e){return e.message=`Unable to read software license file:\n${e.message}`,global.logger.error(e),{}}}get license(){return this.data.license||null}get currentKey(){const e={host:this.service.id,license:this.data.license,version:this.data.version};return JSON.stringify(e)}get desiredKey(){const e={host:this.service.id,license:this.data.license,version:License.EULA_VERSION};return JSON.stringify(e)}get needsSignature(){const e=this.constructor.STATUSES;return[e.NONE,e.INVALID].includes(this.status)}isValidKeyFormat(e){return/^[A-Z0-9]{24}$/.test(e)}applyLicense(e){if(!e)return this.write({license:null,signature:null});e=e.replace(/-/g,"").trim();if(!this.isValidKeyFormat(e))throw new Error("Invalid license key format");return this.write({license:e,signature:void 0})}async sign(){let e;try{e=await fetchJsonWithTimeout(this.constructor.LICENSE_SIGNATURE_URL,{headers:{"Content-Type":"application/json",Authorization:this.authorizationHeader},method:"POST",body:this.desiredKey})}catch(e){throw new Error("License signature server was unable to be reached. Ensure you are connected to the internet with outbound traffic allowed.")}if("error"===e.status)return logger.error(e.message),e;this.write({signature:e.signature});const t="License signature successfully created. Thank you and please enjoy Foundry Virtual Tabletop.";return logger.info(t),this.verify(),{status:"success",message:t}}verify(){this.data.license&&!this.isValidKeyFormat(this.data.license)&&(global.logger.warn("Invalid license key format detected, expiring license file."),fs.unlinkSync(License.path),this.data={});const e=this.data,t=this.constructor.STATUSES;if(!e.signature)return global.logger.warn("Software license requires signature."),this.status=t.NONE;if(isNewerVersion(License.EULA_VERSION,e.version||"0.0.0"))return global.logger.warn("Software license requires EULA signature."),this.status=t.INVALID;const i=crypto.createPublicKey(License.PUBLIC_KEY),s=crypto.createVerify("SHA256");s.write(this.currentKey),s.end();if(s.verify(i,e.signature,"base64"))return global.logger.info("Software license verification succeeded"),this.status=t.VALID;{const e="Software license verification failed. Please confirm your Foundry Virtual Tabletop software license";return global.logger.error(e),this.status=t.INVALID}}write({license:e,signature:t}={}){const i={host:this.service.id,license:void 0!==e?e:this.data.license,version:License.EULA_VERSION,time:new Date,signature:void 0!==t?t:this.data.signature};try{fs.writeFileSync(this.constructor.path,JSON.stringify(i,null,2)),this.data=i}catch(e){const t=new Error(`Failed to write License file:\n${e.message}`);throw t.stack=e.stack,t}}expire(){global.logger.warn("Expiring invalid software license"),fs.unlinkSync(this.constructor.path),this.data=this.constructor.get(),this.status=this.constructor.STATUSES.NONE}}
|
||||
1
resources/app/dist/core/update.mjs
vendored
Normal file
1
resources/app/dist/core/update.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/core/utils.mjs
vendored
Normal file
1
resources/app/dist/core/utils.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{mergeObject,parseUuid}from"../../common/utils/helpers.mjs";export async function fromUuid(e,{invalid:t=!1}={}){let s;const{collection:i,documentId:n,documentType:d,embedded:o}=parseUuid(e);for(s="CompendiumDocument"===i.name&&"Folder"===d?await i.getFolder(n,{strict:!0,dropInvalidEmbedded:!0}):await i.get(n,{strict:!0,dropInvalidEmbedded:!0});s&&o.length>1;){const[e,i]=o.splice(0,2);s=s.getEmbeddedDocument(e,i,{strict:!0,invalid:t}),s instanceof foundry.documents.BaseToken&&o.length&&await s.loadRelatedDocuments()}return s||null}export function tagModelStats(e,{changes:t,user:s,modifiedTime:i}={}){if(!e.schema.has("_stats"))return;const n=e._stats,{release:d,game:o}=global,a={modifiedTime:i??Date.now()};s&&(a.lastModifiedBy=s.id),n.createdTime||t||(a.createdTime=a.modifiedTime),n.coreVersion!==d.version&&(a.coreVersion=d.version),t&&!hasSystemDataChanged(e,t)||(n.systemId!==o.system.id&&(a.systemId=o.system.id),n.systemVersion!==o.system.version&&(a.systemVersion=o.system.version)),t?mergeObject(t,{_stats:a}):e.updateSource({_stats:a})}function hasSystemDataChanged(e,t){if(e.schema.has("system")&&("system"in t||"type"in t))return!0;const s=t.flags;if(s){const e=game.system.id;if(e in s||`-=${e}`in s)return!0}return!1}
|
||||
1
resources/app/dist/database/backend/compendium-folder.mjs
vendored
Normal file
1
resources/app/dist/database/backend/compendium-folder.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default function ServerCompendiumFolderMixin(e){return class extends db.Folder{static _compendium=e;static{Object.defineProperty(this,"_db",{get:()=>e._db})}static get collectionName(){return"folders"}static get sublevel(){return this._db.sublevels.folders}get compendium(){return this.constructor._compendium}static metadata=(()=>foundry.utils.mergeObject(super.metadata,{permissions:{create:this.#e.bind(this),update:this.#e.bind(this),delete:this.#e.bind(this)}},{inplace:!1}))();static async getMany(e,t={}){const i=[];for(let o of e)i.push(await this._compendium.getFolder(o,t));return i}static fromSource(e,t={}){return t.pack=this._compendium.collectionName,super.fromSource(e,t)}static#e(e,t,i){if(((game.compendiumConfiguration||{})[t.pack]||{}).locked??"world"!==t.compendium.package.type)throw new Error(`You may not modify the ${t.pack} Compendium which is currently locked.`);return db.packs.get(t.pack).isOwner(e)}}}
|
||||
1
resources/app/dist/database/backend/embedded-delta.mjs
vendored
Normal file
1
resources/app/dist/database/backend/embedded-delta.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default function EmbeddedDeltaMixin(e){return class extends e{async _preUpdate(e,t,s){if(!1===await super._preUpdate(e,t,s))return!1;const i=this.parent.getEmbeddedCollection(this.parentCollection);i.manages(this.id)||i.set(this.id,this)}batchWrite(e,{restoreDelta:t=!1,...s}={}){const i=this.parent.getEmbeddedCollection(this.parentCollection);t&&!i.manages(this.id)?super.batchDelete(e):super.batchWrite(e,s)}batchDelete(e,{restoreDelta:t=!1}={}){const s=this.parent.getEmbeddedCollection(this.parentCollection);if(!t&&s.isTombstone(this.id)){const t=new foundry.data.TombstoneData({_id:this.id}),{dbKey:s,sublevelName:i}=this;this.constructor.batchWrite(t.toObject(),e,{dbKey:s,sublevelName:i})}else super.batchDelete(e)}}}
|
||||
1
resources/app/dist/database/backend/level-database.mjs
vendored
Normal file
1
resources/app/dist/database/backend/level-database.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import{ClassicLevel}from"classic-level";import SublevelDatabase from"./sublevel-database.mjs";import Semaphore from"../../../common/utils/semaphore.mjs";export default class LevelDatabase extends ClassicLevel{constructor(e,a,{sublevels:t=[],...s}={}){if(!e||!a)throw new Error("You must provide a unique database name and file path location");if(LevelDatabase.#e.has(e))throw new Error(`The database "${e}" is already open and cannot be re-created.`);s.keyEncoding="utf8",s.valueEncoding="json",super(a,s),this.#a=e,LevelDatabase.#e.set(e,this),this.setMaxListeners(Math.max(10,t.length+1));const n={keyEncoding:s.keyEncoding,valueEncoding:s.valueEncoding};for(const e of t)this.#t[e]=this.sublevel(e,n)}static async connect(e,a,{allowRepair:t=!0,...s}={}){const n=new this(e,a,{passive:!0,createIfMissing:!0,...s});try{await n.open(),await n.keys().all(),global.logger.info(`Connected to database "${e}"`)}catch(i){if(await n.close(),i.message=`Failed to connect to database "${e}": ${i.message}`,t)return logger.error(i),LevelDatabase.#s(e,a,s);throw i}return n}static async#s(e,a,t){return logger.warn(`FoundryVTT | Attempting database repair for ${a}`),await this.repair(a),logger.warn(`FoundryVTT | Repair of ${a} complete. Attempting re-connection`),LevelDatabase.connect(e,a,{allowRepair:!1,...t})}semaphore=new Semaphore(1);get name(){return this.#a}#a;get sublevels(){return this.#t}#t={};static get databases(){return LevelDatabase.#e}static#e=new Map;static formatKey(...e){return e.join(".")}async close(...e){if(LevelDatabase.#e.delete(this.#a),"open"===this.status)try{await this.compactFull()}catch(e){e.message=`Unable to compact database ${this.location}: ${e.message}`,logger.error(e)}return super.close(...e)}async clone(e,a){if(this.constructor.databases.has(e)||a===this.location)throw new Error("The cloned database name and location must be unique");const t=await this.constructor.connect(e,a,{sublevels:Object.keys(this.sublevels)}),s=t.batch(),n=this.iterator();for await(const[e,a]of n)s.put(e,a);return await n.close(),await s.write(),t}async destroy(){await this.close(),fs.rmSync(this.location,{recursive:!0})}async compactFull(){const e=this.keys({limit:1,fillCache:!1}),a=await e.next();await e.close();const t=this.keys({limit:1,reverse:!0,fillCache:!1}),s=await t.next();return await t.close(),this.compactRange(a,s,{keyEncoding:"utf8"})}async size(){const e=this.keys({limit:1,fillCache:!1}),a=await e.next();await e.close();const t=this.keys({limit:1,reverse:!0,fillCache:!1}),s=await t.next();return await t.close(),this.approximateSize(a,s,{keyEncoding:"utf8"})}_sublevel(e,a){return new SublevelDatabase(this,e,a)}}
|
||||
1
resources/app/dist/database/backend/server-backend.mjs
vendored
Normal file
1
resources/app/dist/database/backend/server-backend.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/database/backend/server-compendium.mjs
vendored
Normal file
1
resources/app/dist/database/backend/server-compendium.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import ServerCompendiumFolderMixin from"./compendium-folder.mjs";import{tagModelStats}from"../../core/utils.mjs";import*as CONST from"../../../common/constants.mjs";import{PACKAGE_TYPE_MAPPING}from"../../packages/_module.mjs";export default function ServerCompendiumMixin(e,t){return class extends e{static{this._db=void 0,this._dbState=0,this._dbWait=void 0,this.sublevel=void 0}static packData=t;static folderClass=ServerCompendiumFolderMixin(this);static get package(){return packages[this.packData.packageType.titleCase()].get(this.packData.packageName)}static metadata=(()=>foundry.utils.mergeObject(super.metadata,{permissions:{create:this.#e.bind(this),update:this.#e.bind(this),delete:this.#e.bind(this)}},{inplace:!1}))();static get collectionName(){return this.packData.id}static get implementation(){return db.packs.get(this.collectionName)}static get filename(){return this.packData.absPath}static _getSublevelNames(){const e=super._getSublevelNames();return e.push("folders"),e}static async disconnect(){await super.disconnect(),db.packs.delete(this.collectionName)}static fromSource(e,t={}){return t.pack=this.collectionName,super.fromSource(e,t)}static isOwner(e){const t=CONST.DOCUMENT_OWNERSHIP_LEVELS;return(e.isGM?t.OWNER:this.getUserLevel(e))>=t.OWNER}static getUserLevel(e){const t=CONST.DOCUMENT_OWNERSHIP_LEVELS;let i=t.NONE;const a=(game.compendiumConfiguration||{})[this.collectionName]||{},s=a?.ownership??this.packData?.ownership??{...PACKAGE_TYPE_MAPPING.module.schema.getField("packs.ownership").initial};for(const[a,o]of Object.entries(s))e.hasRole(a)&&(i=Math.max(i,t[o]));return i}static#e(e,t,i){if(((game.compendiumConfiguration||{})[t.collectionName]||{}).locked??"world"!==t.constructor.package.type)throw new Error(`You may not modify the ${t.collectionName} Compendium which is currently locked.`);return db.packs.get(t.collectionName).isOwner(e)}static async deleteCompendium(){await this.disconnect(),await fs.promises.rm(this.filename,{force:!0,recursive:!0}),await fs.promises.rm(`${this.filename}.db`,{force:!0}),logger.info(`Deleted Compendium Pack ${this.collectionName}`)}static async getIndex(e){if(!e)throw new Error("You must provide an array of index fields to retrieve");return this.connected||await this.connect(),this.database.get(this,{query:{},index:!0,indexFields:e})}static async getFolders(){return this.db.sublevels.folders.find()}static async getFolder(e,t={}){const i=await this.db.sublevels.folders.get(e);if(void 0!==i)return this.folderClass.fromSource(i,t);if(!0===t.strict)throw new Error(`The Folder [${e}] does not exist in ${this.collectionName}.`)}static async migrate({user:e,...t}={}){logger.info(`Migrating ${this.collectionName} Compendium to updated system template version.`),this.connected||await this.connect();const i=await this.find(),a=this.db.batch();for(let t of i)this.hasTypeData&&t.updateSource({system:t.migrateSystemData()}),tagModelStats(t,{user:e}),t.batchWrite(a),logger.info(`Migrated ${this.documentName} ${t.name} in Compendium pack ${this.collectionName}`);await a.write(),logger.info(`Migrated all ${i.length} ${this.documentName} Documents in Compendium pack ${this.collectionName}`)}}}
|
||||
1
resources/app/dist/database/backend/server-document.mjs
vendored
Normal file
1
resources/app/dist/database/backend/server-document.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/database/backend/sublevel-database.mjs
vendored
Normal file
1
resources/app/dist/database/backend/sublevel-database.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{AbstractSublevel}from"abstract-level";import{filterObject,getType,mergeObject,randomID}from"../../../common/utils/helpers.mjs";export default class SublevelDatabase extends AbstractSublevel{async createNewId(){for(;;){const e=randomID(16);if(!await this.has(e))return e}}prefixKey(e,t="utf8"){return super.prefixKey(e,t)}async get(e,t={},r){try{return await super.get(e,t,r)}catch(e){return}}async put(e,t,r={},a){return await super.put(e,t,r,a),t}async has(e){const t=this.keys({gte:e,lte:e,limit:1,fillCache:!1}),r=await t.next();return await t.close(),!!r}async delMany(e=[]){const t=await this.getMany(e,{fillCache:!1}),r=this.batch();for(const[a,s]of t.entries()){const t=e[a];s&&r.del(t)}return await r.write(),t}async find(e,{project:t,map:r,sort:a}={}){e=SublevelDatabase.#e(e);const s=[],i=await this.values({fillCache:!1}).all();for(let a of i)SublevelDatabase.#t(a,e)&&(t&&(a=filterObject(a,t)),r&&(a=await r(a)),s.push(a));return a&&("string"==typeof a&&(a={[a]:1}),s.sort(((e,t)=>SublevelDatabase.#r(e,t,a)))),s}async findOne(e,t){const r=await this.find(e,t);if(r.length)return r.length>1&&global.logger.warn(`Multiple results found for query "${JSON.stringify(e)}"`),r[0]}async findUpdate(e,t){e=SublevelDatabase.#e(e);const r=this.batch(),a=[];for(const[s,i]of await this.iterator({fillCache:!1}).all())SublevelDatabase.#t(i,e)&&(mergeObject(i,t),r.put(s,i),a.push(i));return await r.write(),a}async findDelete(e){e=SublevelDatabase.#e(e);const t=this.batch(),r=[];for(const[a,s]of await this.iterator({fillCache:!1}).all())SublevelDatabase.#t(s,e)&&(t.del(a),r.push(s));return await t.write(),r}static#r(e,t,r={}){for(const[a,s]of Object.entries(r)){const r=e[a],i=t[a];let n=0;if("string"==typeof r?n=r.compare(i)*s:"number"==typeof i&&(n=(r-i)*s),0!==n)return n}return 0}static#e(e){if(!e)return;const t=/^([A-z]+)__([a-z]+)$/;for(const[r,a]of Object.entries(e)){const s=r.match(t);if(s){const[t,i,n]=s;if(delete e[r],"in"===n)e[i]=new QueryOperation(i,a,SublevelDatabase.#a)}else"Object"===getType(a)&&SublevelDatabase.#e(a)}return e}static#t(e,t){if(!t)return!0;for(const[r,a]of Object.entries(t)){const t=e[r];if(a instanceof QueryOperation){if(!a.test(t))return!1}else if("Object"===getType(a)){if(!SublevelDatabase.#t(t,a))return!1}else if(t!==a)return!1}return!0}static#a(e,t){if(!Array.isArray(t))throw new Error("You must provide an array of target values when querying field__in");return t.includes(e)}}class QueryOperation{constructor(e,t,r){Object.defineProperties(this,{key:{value:e,writable:!1},target:{value:t,writable:!1},comparator:{value:r,writable:!1}})}test(e){return this.comparator(e,this.target)}}
|
||||
1
resources/app/dist/database/database.mjs
vendored
Normal file
1
resources/app/dist/database/database.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import"./fields-extensions.mjs";import{COMPENDIUM_DOCUMENT_TYPES,COMPATIBILITY_MODES}from"../../common/constants.mjs";import LevelDatabase from"./backend/level-database.mjs";import ServerDatabaseBackend from"./backend/server-backend.mjs";import ServerCompendiumMixin from"./backend/server-compendium.mjs";import ActiveEffect from"./documents/active-effect.mjs";import Actor from"./documents/actor.mjs";import ActorDelta from"./documents/actor-delta.mjs";import Adventure from"./documents/adventure.mjs";import AmbientLight from"./documents/ambient-light.mjs";import AmbientSound from"./documents/ambient-sound.mjs";import Card from"./documents/card.mjs";import Cards from"./documents/cards.mjs";import ChatMessage from"./documents/chat-message.mjs";import Combat from"./documents/combat.mjs";import Combatant from"./documents/combatant.mjs";import Drawing from"./documents/drawing.mjs";import FogExploration from"./documents/fog.mjs";import Folder from"./documents/folder.mjs";import Item from"./documents/item.mjs";import JournalEntry from"./documents/journal.mjs";import JournalEntryPage from"./documents/journal-page.mjs";import Playlist from"./documents/playlist.mjs";import MeasuredTemplate from"./documents/measured-template.mjs";import Note from"./documents/note.mjs";import Region from"./documents/region.mjs";import RegionBehavior from"./documents/region-behavior.mjs";import Scene from"./documents/scene.mjs";import Setting from"./documents/setting.mjs";import TableResult from"./documents/table-result.mjs";import Tile from"./documents/tile.mjs";import Token from"./documents/token.mjs";import User from"./documents/user.mjs";import Wall from"./documents/wall.mjs";import{Macro,PlaylistSound,RollTable}from"./documents/others.mjs";export const DatabaseBackend=new ServerDatabaseBackend;export const documents=[Actor,Cards,ChatMessage,Combat,FogExploration,Folder,Item,JournalEntry,Macro,Playlist,RollTable,Scene,Setting,User];export function getDocumentClass(e){return global.db[e]}export async function disconnect(){const e=[];for(const t of packs.values())e.push(t.disconnect());packs.clear();for(const t of documents)e.push(t.disconnect()),t.clearSanitizedFields();const t=await Promise.allSettled(e);e.length=0;for(let t of LevelDatabase.databases.values())e.push(t.close());const o=await Promise.allSettled(e);for(const e of t.concat(o))"rejected"===e.status&&global.logger.error(e.reason)}export const packs=new Map;export function defineCompendium(e){_validateCompendiumMetadata(e);const t=global.db[e.type],o=ServerCompendiumMixin(t,e);return db.packs.set(o.collectionName,o),o}function _validateCompendiumMetadata(e={}){if(!("name"in e))throw new Error("Compendium packs must define a canonical name");if(!("path"in e))throw new Error("Compendium packs must specify their relative file path.");if(!("type"in e))throw new Error("Compendium packs must specify the document type they contain.");if(!("absPath"in e))throw new Error("An absolute file path must be provided when a new Compendium is defined.");if(!COMPENDIUM_DOCUMENT_TYPES.includes(e.type))throw new Error(`Compendium ${e.label} is configured for an invalid Document type ${e.type}`);return!0}globalThis.CONFIG={compatibility:{mode:COMPATIBILITY_MODES.WARNING,includePatterns:[],excludePatterns:[]},DatabaseBackend:DatabaseBackend,Actor:{documentClass:Actor},Adventure:{documentClass:foundry.documents.BaseAdventure},Cards:{documentClass:Cards},ChatMessage:{documentClass:ChatMessage},Combat:{documentClass:Combat},FogExploration:{documentClass:FogExploration},Folder:{documentClass:Folder},Item:{documentClass:Item},JournalEntry:{documentClass:JournalEntry},JournalEntryPage:{documentClass:JournalEntryPage},Macro:{documentClass:Macro},Playlist:{documentClass:Playlist},RollTable:{documentClass:RollTable},Scene:{documentClass:Scene},Setting:{documentClass:Setting},User:{documentClass:User},ActiveEffect:{documentClass:ActiveEffect},Card:{documentClass:Card},TableResult:{documentClass:TableResult},PlaylistSound:{documentClass:PlaylistSound},AmbientLight:{documentClass:AmbientLight},AmbientSound:{documentClass:AmbientSound},Combatant:{documentClass:Combatant},Drawing:{documentClass:Drawing},MeasuredTemplate:{documentClass:MeasuredTemplate},Note:{documentClass:Note},Region:{documentClass:Region},RegionBehavior:{documentClass:RegionBehavior},Tile:{documentClass:Tile},Token:{documentClass:Token},Wall:{documentClass:Wall},ActorDelta:{documentClass:ActorDelta}};export{Actor,ActiveEffect,Adventure,AmbientLight,AmbientSound,Card,Cards,ChatMessage,Combat,Combatant,Drawing,FogExploration,Folder,Item,JournalEntry,JournalEntryPage,MeasuredTemplate,Note,Playlist,PlaylistSound,Scene,Setting,Macro,RollTable,Region,RegionBehavior,TableResult,Tile,Token,ActorDelta,User,Wall};
|
||||
1
resources/app/dist/database/documents/active-effect.mjs
vendored
Normal file
1
resources/app/dist/database/documents/active-effect.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import ServerDocumentMixin from"../backend/server-document.mjs";import BaseActiveEffect from"../../../common/documents/active-effect.mjs";import{getType}from"../../../common/utils/helpers.mjs";export default class ActiveEffect extends(ServerDocumentMixin(BaseActiveEffect)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateSystemDataInChanges,version:12},{fn:migrateOriginOwnedItem,version:12}]}function migrateSystemDataInChanges(e){if(!Array.isArray(e.changes))return!1;let t=!1;for(const r of e.changes)"Object"===getType(r)&&"string"==typeof r.key&&r.key.startsWith("data.")&&(r.key=r.key.replace(/^data\./,"system."),t=!0);return t}function migrateOriginOwnedItem(e){if("string"!=typeof e.origin)return!1;const t=e.origin.split(".").map((e=>"OwnedItem"===e?"Item":e)).join(".");return e.origin!==t&&(e.origin=t,!0)}
|
||||
1
resources/app/dist/database/documents/actor-delta.mjs
vendored
Normal file
1
resources/app/dist/database/documents/actor-delta.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import ServerDocumentMixin from"../backend/server-document.mjs";import BaseActorDelta from"../../../common/documents/actor-delta.mjs";import*as fields from"../../../common/data/fields.mjs";import Actor from"./actor.mjs";export default class ActorDelta extends(ServerDocumentMixin(BaseActorDelta)){static _migrationRegistry=Actor._migrationRegistry;static isDelta=!0;static _getTemplateFields(e){return e.Actor||{}}async loadRelatedDocuments(){return this.parent.loadRelatedDocuments()}async _preUpdate(e,t,r){if(t.restoreDelta){for(const t of Object.keys(e))delete e[t];for(const t of this.schema)t instanceof fields.DocumentIdField||(t instanceof fields.EmbeddedCollectionField?e[t.name]=[]:e[t.name]=t.initial);e._id=this.id}return super._preUpdate(e,t,r)}_onDelete(e,t){return super._onDelete(e,t),delete this.parent.delta,this.parent.recreateActorDelta()}}
|
||||
1
resources/app/dist/database/documents/actor.mjs
vendored
Normal file
1
resources/app/dist/database/documents/actor.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseActor from"../../../common/documents/actor.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import{IMAGE_FILE_EXTENSIONS,VIDEO_FILE_EXTENSIONS}from"../../../common/constants.mjs";import{getType}from"../../../common/utils/helpers.mjs";import Files from"../../files/files.mjs";import Token from"./token.mjs";export default class Actor extends(ServerDocumentMixin(BaseActor)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateV10Fields,version:12},...Token._migrationRegistry.map((e=>({...e,fn:t=>{const r=t.prototypeToken;return"Object"===getType(r)&&e.fn(r)}})))];static socketListeners(e){e.on("requestTokenImages",this.#e.bind(this,e))}static async#e(e,t,{pack:r=null},o){const s=r?db.packs.get(r):db.Actor;if(!s.ready)return o({error:`The "${r}" database is not yet connected.`});const n=await s.get(t),i=n?.testUserPermission(e.user,"OWNER");if(!e.user.hasPermission("FILES_BROWSE")&&!i)return o({error:`You do not have permission to query wildcard token images for Actor [${t}].`});const a=n.prototypeToken;if(!a.randomImg)return o({files:[a.texture.src]});const{source:c,pattern:m,browseOptions:d}=Files.parseWildcardPath(a.texture.src);d.isAdmin=!0,d.target=m,d.extensions=Object.keys(IMAGE_FILE_EXTENSIONS).concat(Object.keys(VIDEO_FILE_EXTENSIONS)).map((e=>`.${e}`));config.files.storages[c].getFiles(d).then(o).catch((e=>o({error:e.message})))}}function migrateV10Fields(e){const t=Actor._addDataFieldMigration(e,"data","system"),r=Actor._addDataFieldMigration(e,"token","prototypeToken");return t||r}
|
||||
1
resources/app/dist/database/documents/adventure.mjs
vendored
Normal file
1
resources/app/dist/database/documents/adventure.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseAdventure from"../../../common/documents/adventure.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Adventure extends(ServerDocumentMixin(BaseAdventure)){static _migrateEmbeddedRecords(e,r){let t=super._migrateEmbeddedRecords(e,r);for(const[o,n]of Object.entries(this.contentFields)){const s=e[o];if(Array.isArray(s))for(const e of s){const o=n._migrateRecord(e,r);t||=o}}return t}}
|
||||
1
resources/app/dist/database/documents/ambient-light.mjs
vendored
Normal file
1
resources/app/dist/database/documents/ambient-light.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseAmbientLight from"../../../common/documents/ambient-light.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import{getType}from"../../../common/utils/helpers.mjs";export default class AmbientLight extends(ServerDocumentMixin(BaseAmbientLight)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateFieldNameChanges,version:12},{fn:migrateNegativeRadius,version:12},{fn:migrateGradualToAttenuation,version:12},{fn:migrateZeroAngle,version:12},{fn:migrateSourceTypeFlags,version:12},{fn:migrateNegativeLuminosity,version:"12.319"}]}function migrateFieldNameChanges(i){let e=!1;const t={darknessThreshold:"darkness.min",dim:"config.dim",bright:"config.bright",angle:"config.angle",tintColor:"config.color",tintAlpha:"config.alpha",lightAnimation:"config.animation",darkness:"config.darkness"};for(const[n,o]of Object.entries(t)){const t=AmbientLight._addDataFieldMigration(i,n,o);e||=t}return e}function migrateSourceTypeFlags(i){return"t"in i&&("walls"in i||(i.walls="u"!==i.t),"vision"in i||(i.vision="l"!==i.t),delete i.t,!0)}function migrateNegativeRadius(i){const e=i.config;if("Object"!==getType(e))return!1;let t=!1;return e.dim<0&&(e.dim=Math.abs(e.dim),t=!0),e.bright<0&&(e.bright=Math.abs(e.bright),t=!0),!!t&&(e.luminosity=-1*Math.abs(e.luminosity??.5),!0)}function migrateGradualToAttenuation(i){const e=i.config;return"Object"===getType(e)&&("gradual"in e&&(e.attenuation=e.gradual?.5:.3,delete e.gradual,!0))}function migrateZeroAngle(i){return"Object"===getType(i.config)&&(0===i.config.angle&&(i.config.angle=360,!0))}function migrateNegativeLuminosity(i){const e=i.config;return"Object"===getType(e)&&(e.luminosity<0&&(e.luminosity=.5,e.negative=!0,!0))}
|
||||
1
resources/app/dist/database/documents/ambient-sound.mjs
vendored
Normal file
1
resources/app/dist/database/documents/ambient-sound.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseAmbientSound from"../../../common/documents/ambient-sound.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class AmbientSound extends(ServerDocumentMixin(BaseAmbientSound)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateWallAttributes,version:12}]}function migrateWallAttributes(e){return"t"in e&&("walls"in e||(e.walls="l"===e.t),delete e.t,!0)}
|
||||
1
resources/app/dist/database/documents/card.mjs
vendored
Normal file
1
resources/app/dist/database/documents/card.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseCard from"../../../common/documents/card.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Card extends(ServerDocumentMixin(BaseCard)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateV10Fields,version:12}]}function migrateV10Fields(e){return Card._addDataFieldMigration(e,"data","system")}
|
||||
1
resources/app/dist/database/documents/cards.mjs
vendored
Normal file
1
resources/app/dist/database/documents/cards.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import ServerDocumentMixin from"../backend/server-document.mjs";import BaseCards from"../../../common/documents/cards.mjs";export default class Cards extends(ServerDocumentMixin(BaseCards)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateV10Fields,version:12},{fn:migrateBaseToDeck,version:12}]}function migrateV10Fields(e){return Cards._addDataFieldMigration(e,"data","system")}function migrateBaseToDeck(e){return e.type===CONST.BASE_DOCUMENT_TYPE&&(e.type="deck",!0)}
|
||||
1
resources/app/dist/database/documents/chat-message.mjs
vendored
Normal file
1
resources/app/dist/database/documents/chat-message.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseChatMessage from"../../../common/documents/chat-message.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class ChatMessage extends(ServerDocumentMixin(BaseChatMessage)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateRolls,version:12}]}function migrateRolls(e){return"roll"in e&&("rolls"in e||(e.rolls=[e.roll]),delete e.roll,!0)}
|
||||
1
resources/app/dist/database/documents/combat.mjs
vendored
Normal file
1
resources/app/dist/database/documents/combat.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseCombat from"../../../common/documents/combat.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Combat extends(ServerDocumentMixin(BaseCombat)){static get collection(){return"combat"}_onUpdate(e,t,a){if(super._onUpdate(e,t,a),Number.isNumeric(t.advanceTime)&&!t.worldTime&&(t.worldTime={delta:t.advanceTime}),"object"==typeof t.worldTime){const{delta:e,...m}=t.worldTime;Number.isNumeric(e)&&db.Setting.advanceTime(e,m,a)}}static async _onDeleteScene(e){const t=(await this.find({scene:e.id})).map((e=>e.id));await this.sublevel.delMany(t),db.DatabaseBackend.emit(this.documentName,"delete",t)}}
|
||||
1
resources/app/dist/database/documents/combatant.mjs
vendored
Normal file
1
resources/app/dist/database/documents/combatant.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseCombatant from"../../../common/documents/combatant.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Combatant extends(ServerDocumentMixin(BaseCombatant)){async loadRelatedDocuments(){if(void 0===this.actor){const t=await db.Actor.get(this.actorId);this.actor=t||null}}static async _onCreateOperation(t,a,e){return await Combatant.#t(a),await Combatant.#a(a),super._onCreateOperation(t,a,e)}static async _onUpdateOperation(t,a,e){return await Combatant.#t(a),await Combatant.#a(a),super._onUpdateOperation(t,a,e)}static async _onDeleteOperation(t,a,e){return await Combatant.#t(a),super._onDeleteOperation(t,a,e)}static async#t(t){if("combatTurn"in t){const a=t.parent;if(t.combatTurn===a.turn)return;a.updateSource({turn:t.combatTurn}),await a.save({writeEmbedded:!1})}}static async#a(t){const a=t.parent;a.scene&&a.combatants.some((t=>t.sceneId&&t.sceneId!==a.scene))&&(a.updateSource({scene:null}),await a.save({writeEmbedded:!1}),db.DatabaseBackend.emit(a.documentName,"update",[{_id:a.id,scene:null}]))}}
|
||||
1
resources/app/dist/database/documents/drawing.mjs
vendored
Normal file
1
resources/app/dist/database/documents/drawing.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseDrawing from"../../../common/documents/drawing.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Drawing extends(ServerDocumentMixin(BaseDrawing)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateV10Fields,version:12},{fn:migrateNullValues,version:12},{fn:migrateInvisible,version:12},{fn:migrateInterface,version:"12.317"}]}function migrateV10Fields(t){const e={t:"r",f:"p"},i=Drawing._addDataFieldMigration(t,"type","shape.type",(t=>e[t.type]??t.type)),r=Drawing._addDataFieldMigration(t,"width","shape.width"),n=Drawing._addDataFieldMigration(t,"height","shape.height"),o=Drawing._addDataFieldMigration(t,"points","shape.points",(t=>Array.isArray(t.points)?t.points.flat():t.points));return i||r||n||o}function migrateNullValues(t){let e=!1;return null===t.fillColor&&(t.fillColor="#ffffff",e=!0),null===t.fontSize&&(t.fontSize=48,e=!0),null===t.strokeColor&&(t.strokeColor="#ffffff",e=!0),null===t.strokeWidth&&(t.strokeWidth=0,e=!0),null===t.textColor&&(t.textColor="#ffffff",e=!0),e}function migrateInvisible(t){let{text:e,textAlpha:i,fillType:r,fillAlpha:n,strokeWidth:o,strokeAlpha:a}=t;e??="",i??=1,r??=CONST.DRAWING_FILL_TYPES.NONE,n??=1,o??=8,a??=1;const l=""!==e&&i>0,f=r!==CONST.DRAWING_FILL_TYPES.NONE&&n>0;return!(l||f||o>0&&a>0)&&(t.strokeWidth=8,t.strokeColor="#ffffff",t.strokeAlpha=1,!0)}function migrateInterface(t){return t.interface=!!t.text,!0}
|
||||
1
resources/app/dist/database/documents/fog.mjs
vendored
Normal file
1
resources/app/dist/database/documents/fog.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseFogExploration from"../../../common/documents/fog-exploration.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class FogExploration extends(ServerDocumentMixin(BaseFogExploration)){_onCreate(e,o,t){return o.broadcast=!1,super._onCreate(e,o,t)}_onUpdate(e,o,t){return o.broadcast=!1,super._onUpdate(e,o,t)}_onDelete(e,o){return e.broadcast=!1,super._onDelete(e,o)}static async find(e={},o={}){const t=await super.find(e,o);if(!0===o.expireOthers){const e=t.length?t.pop():null;return t.length&&await this.sublevel.delMany(t.map((e=>e._id))),e}return t}static socketListeners(e){e.on("resetFog",this.#e.bind(e))}static async#e(e){if(!this.user.isGM)throw new Error("You do not have permission to reset Fog of War for this scene");const o=(await FogExploration.find({scene:e})).map((e=>e.id));await FogExploration.sublevel.delMany(o),global.logger.info(`Reset ${o.length} FogExploration documents for Scene ${e}`),global.express.io.emit("resetFog",{sceneId:e}),db.DatabaseBackend.emit(FogExploration.documentName,"delete",o)}}
|
||||
1
resources/app/dist/database/documents/folder.mjs
vendored
Normal file
1
resources/app/dist/database/documents/folder.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseFolder from"../../../common/documents/folder.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import{SORT_INTEGER_DENSITY}from"../../../common/constants.mjs";export default class Folder extends(ServerDocumentMixin(BaseFolder)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateParentToFolder,version:12}];async getSubfolders(e=!1,t){if((t=t||new Set).has(this.id))return[];let r=await this.constructor.find({folder:this.id});if(t.add(this.id),e&&r.length)for(let e of r){const i=await e.getSubfolders(!0,t);r=r.concat(i)}return r}async _preCreate(e,t,r){if(!Number.isFinite(this.sort)){const e=await Folder.find({type:this.type});let t=Math.max(...e.map((e=>e.sort||0)));this.updateSource({sort:t+SORT_INTEGER_DENSITY})}if(e.folder){const t=e.pack?db.packs.get(e.pack).folders:db.Folder;let r=await t.get(e.folder);if(!r)return;const i=e.pack?CONST.FOLDER_MAX_DEPTH-1:CONST.FOLDER_MAX_DEPTH;let a=1;for(;r?.folder;)r=await t.get(r.folder),a++;if(a>=i)throw new Error(`You may not nest Folders more than ${i} levels deep.`)}if(this.compendium&&this.compendium.packData.type!==this.type)throw new Error(`Attempted to create a Folder for ${this.type} Documents in a compendium that only allows for ${this.compendium.packData.type} Documents.`);return super._preCreate(e,t,r)}async _preUpdate(e,t,r){if(!1===await super._preUpdate(e,t,r))return!1;if(e.parent&&e.parent===this.id)throw new Error("You cannot assign a Folder to be it's own parent")}async _preDelete(e,t){if(!1===await super._preDelete(e,t))return!1;const r=this.folder||null,{deleteContents:i,deleteSubfolders:a}=e,o=[],n=[],s=await this.getSubfolders(!0);for(let e of s)a?o.push(e.id):n.push(e.id);if(o.length&&(await this.constructor.sublevel.delMany(o),db.DatabaseBackend.emit(this.documentName,"delete",o,{pack:this.pack,render:!0},t)),n.length){await this.constructor.sublevel.findUpdate({_id__in:Array.from(n)},{folder:r});const e=n.map((e=>({_id:e,folder:r})));db.DatabaseBackend.emit(this.documentName,"update",e,{pack:this.pack,render:!0},t)}if("Compendium"===this.type)return;o.push(this.id);const d=this.pack?this.compendium.sublevel:db[this.type].implementation.sublevel;let l=[];if(i)l=await d.findDelete({folder__in:Array.from(o)}),l.length&&db.DatabaseBackend.emit(this.type,"delete",l.map((e=>e._id)),{pack:this.pack,render:!0},t);else{const e=await d.findUpdate({folder__in:Array.from(o)},{folder:r});if(e.length){const i=e.map((e=>({_id:e._id,folder:r})));db.DatabaseBackend.emit(this.type,"update",i,{pack:this.pack,render:!0},t)}}logger.info([`Deleting Folder [${this.id}] also deletes:`,o.length>1?o.length-1+" subfolders":"",l.length?`and ${l.length} ${this.type} documents`:""].filterJoin(" "))}}function migrateParentToFolder(e){return Folder._addDataFieldMigration(e,"parent","folder")}
|
||||
1
resources/app/dist/database/documents/item.mjs
vendored
Normal file
1
resources/app/dist/database/documents/item.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseItem from"../../../common/documents/item.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Item extends(ServerDocumentMixin(BaseItem)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateV10Fields,version:12}]}function migrateV10Fields(e){return Item._addDataFieldMigration(e,"data","system")}
|
||||
1
resources/app/dist/database/documents/journal-page.mjs
vendored
Normal file
1
resources/app/dist/database/documents/journal-page.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import ServerDocumentMixin from"../backend/server-document.mjs";import BaseJournalEntryPage from"../../../common/documents/journal-entry-page.mjs";import showdown from"showdown";import{cleanHTML}from"../validators.mjs";export default class JournalEntryPage extends(ServerDocumentMixin(BaseJournalEntryPage)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateBaseToText,version:12}];static#t=(()=>(Object.entries(CONST.SHOWDOWN_OPTIONS).forEach((([t,e])=>showdown.setOption(t,e))),new showdown.Converter))();async _preCreate(t,e,r){if(!1===await super._preCreate(t,e,r))return!1;this.text.format===CONST.JOURNAL_ENTRY_PAGE_FORMATS.MARKDOWN?this.updateSource({"text.content":this.#e(this.text.markdown)}):this.updateSource({"text.markdown":void 0})}async _preUpdate(t,e,r){if(!1===await super._preUpdate(t,e,r))return!1;if("text"in t){const e="format"in t.text?t.text.format??CONST.JOURNAL_ENTRY_PAGE_FORMATS.HTML:this.text.format;if(e===CONST.JOURNAL_ENTRY_PAGE_FORMATS.MARKDOWN){const r="markdown"in t.text?t.text.markdown:this.text.markdown;t.text.content=this.#e(r,e),void 0!==this.text.content&&(t.text.content||="")}else this.text.markdown?t.text.markdown="":"markdown"in t.text&&delete t.text.markdown}}#e(t,e){const r=CONST.JOURNAL_ENTRY_PAGE_FORMATS,n=(e??this.text.format)===r.MARKDOWN&&t;if("text"!==this.type||!n)return;let o=JournalEntryPage.#t.makeHtml(t);return cleanHTML(o)}}function migrateBaseToText(t){return t.type===CONST.BASE_DOCUMENT_TYPE&&(t.type="text",!0)}
|
||||
1
resources/app/dist/database/documents/journal.mjs
vendored
Normal file
1
resources/app/dist/database/documents/journal.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseJournalEntry from"../../../common/documents/journal-entry.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import*as CONST from"../../../common/constants.mjs";export default class JournalEntry extends(ServerDocumentMixin(BaseJournalEntry)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateJournalEntryPages,version:12}];static socketListeners(e){e.on("showEntry",this.#e.bind(e)),e.on("shareImage",this.#t.bind(e))}static#e(e,{force:t=!1,users:n=[]},r){if(n.length)for(const r of n){const n=game.users.find((e=>e.id===r));n&&n.sockets.forEach((n=>n.emit("showEntry",e,t)))}else this.broadcast.emit("showEntry",e,t);r(!0)}static#t({users:e,...t}={}){if(e?.length)for(const n of e){const e=game.users.find((e=>e.id===n));e&&e.sockets.forEach((e=>e.emit("shareImage",t)))}else this.broadcast.emit("shareImage",t)}}function migrateJournalEntryPages(e){if(!("img"in e)&&!("content"in e))return!1;if(Array.isArray(e.pages)&&e.pages.length>0)return delete e.img,delete e.content,!0;e.pages=[];const t=e.img&&e.content;return e.img&&e.pages.push({name:`${t?"Figure: ":""}${e.name}`,type:"image",src:e.img,title:{show:!1}}),e.content&&e.pages.push({name:e.name,type:"text",title:{show:!1},text:{format:CONST.JOURNAL_ENTRY_PAGE_FORMATS.HTML,content:e.content}}),delete e.img,delete e.content,!0}
|
||||
1
resources/app/dist/database/documents/measured-template.mjs
vendored
Normal file
1
resources/app/dist/database/documents/measured-template.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseMeasuredTemplate from"../../../common/documents/measured-template.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class MeasuredTemplate extends(ServerDocumentMixin(BaseMeasuredTemplate)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateNegativeDistanceAndWidth,version:12},{fn:migrateNullAngle,version:12},{fn:migrateNullFillColor,version:12}]}function migrateNegativeDistanceAndWidth(e){let t=!1;return"distance"in e&&e.distance<0&&(e.distance=Math.abs(e.distance),t=!0),"width"in e&&e.width<0&&(e.width=Math.abs(e.width),t=!0),t}function migrateNullAngle(e){return null===e.angle&&"cone"===e.t&&(e.angle=90,!0)}function migrateNullFillColor(e){return null===e.fillColor&&(e.fillColor="#ff0000",!0)}
|
||||
1
resources/app/dist/database/documents/note.mjs
vendored
Normal file
1
resources/app/dist/database/documents/note.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseNote from"../../../common/documents/note.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Note extends(ServerDocumentMixin(BaseNote)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateTextureData,version:12}];async loadRelatedDocuments(){this.entry||(this.entry=await db.JournalEntry.get(this.entryId))}}function migrateTextureData(t){const e=Note._addDataFieldMigration(t,"icon","texture.src"),r=Note._addDataFieldMigration(t,"iconTint","texture.tint");return e||r}
|
||||
1
resources/app/dist/database/documents/others.mjs
vendored
Normal file
1
resources/app/dist/database/documents/others.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import ServerDocumentMixin from"../backend/server-document.mjs";import BaseMacro from"../../../common/documents/macro.mjs";import BasePlaylistSound from"../../../common/documents/playlist-sound.mjs";import BaseRollTable from"../../../common/documents/roll-table.mjs";export class Macro extends(ServerDocumentMixin(BaseMacro)){}export class PlaylistSound extends(ServerDocumentMixin(BasePlaylistSound)){}export class RollTable extends(ServerDocumentMixin(BaseRollTable)){}
|
||||
1
resources/app/dist/database/documents/playlist.mjs
vendored
Normal file
1
resources/app/dist/database/documents/playlist.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BasePlaylist from"../../../common/documents/playlist.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import{handleCustomSocket}from"../../server/sockets.mjs";export default class Playlist extends(ServerDocumentMixin(BasePlaylist)){static socketListeners(o){o.on("playAudio",handleCustomSocket.bind(o,"playAudio")),o.on("playAudioPosition",handleCustomSocket.bind(o,"playAudioPosition")),o.on("preloadAudio",(e=>o.broadcast.emit("preloadAudio",e)))}}
|
||||
1
resources/app/dist/database/documents/region-behavior.mjs
vendored
Normal file
1
resources/app/dist/database/documents/region-behavior.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseRegionBehavior from"../../../common/documents/region-behavior.mjs";import{setProperty}from"../../../common/utils/helpers.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class RegionBehavior extends(ServerDocumentMixin(BaseRegionBehavior)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateExecuteMacroEveryone,version:"12.326"}]}function migrateExecuteMacroEveryone(e){return"executeMacro"===e.type&&(setProperty(e,"system.everyone",!0),!0)}
|
||||
1
resources/app/dist/database/documents/region.mjs
vendored
Normal file
1
resources/app/dist/database/documents/region.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseRegion from"../../../common/documents/region.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Region extends(ServerDocumentMixin(BaseRegion)){static socketListeners(e){e.on("regionEvent",this.#e.bind(this))}static#e(e){global.express.io.emit("regionEvent",e)}}
|
||||
1
resources/app/dist/database/documents/scene.mjs
vendored
Normal file
1
resources/app/dist/database/documents/scene.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseScene from"../../../common/documents/scene.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import{getProperty,getType,setProperty}from"../../../common/utils/helpers.mjs";export default class Scene extends(ServerDocumentMixin(BaseScene)){static isCached=!0;static _migrationRegistry=[...super._migrationRegistry,{fn:migrateV10Fields,version:12},{fn:migrateLegacyHexFlag,version:12},{fn:migrateRangesInLegacyHex,version:12},{fn:migrateHexGridAlpha,version:12},{fn:migrateOverheadTiles,version:12},{fn:migrateGlobalLightLuminosity,version:"12.325"}];async _preCreate(e,t,r){return this.active&&await this.activate(),super._preCreate(e,t,r)}async _preUpdate(e,t,r){if(!1===await super._preUpdate(e,t,r))return!1;e.active&&await this.activate()}_onDelete(e,t){super._onDelete(e,t),db.Combat._onDeleteScene(this),db.FogExploration.sublevel.findDelete({scene:this.id})}async activate(){logger.info(`Activating scene ${this.name} [${this.id}]`);(await this.constructor.sublevel.findUpdate({active:!0},{active:!1})).forEach((({_id:e})=>{if(e===this.id)return;const t=game.documentCache.get(this.constructor.documentName,e);t&&(t.updateSource({active:!1}),game.documentCache.set(t))}))}static async get(e,t,r){const i=game.documentCache.get(this.documentName,e)??await super.get(e,t,r);return game.documentCache.set(i),i}static async getMany(e,t){const r=[],i=[];for(const t of e){const e=game.documentCache.get(this.documentName,t);e?i.push(e):r.push(t)}return r.length?i.concat(await super.getMany(e,t)):i}static socketListeners(e){e.on("preloadScene",this.#e.bind(e)),e.on("pullToScene",this.#t.bind(e))}static#e(e,t){this.broadcast.emit("preloadScene",e),t(e)}static async#t(e,t){if(!this.user.isGM)return;const r=(await db.User.get(t,{strict:!0})).sockets;r.length&&r.forEach((t=>this.server.to(t.id).emit("pullToScene",e)))}static async migrateSystem(){const{logger:e}=global;e.info("Migrating Scene documents to the latest game system data model");const t=await this.find({},{}),r=this.db.batch();for(const i of t){for(const t of i.tokens){try{await t.loadRelatedDocuments()}catch(t){e.error(t);continue}const i=t.delta?._source.items||[];if(t.actorLink||!i.length)continue;const o=[];for(const t of i)try{if(t._tombstone)return t;const e=db.Item.fromSource(t);e.updateSource({system:e.migrateSystemData()}),o.push(e.toObject())}catch(t){e.error(t)}t.delta._source.items=o,t.batchWrite(r)}i.batchWrite(r,{writeEmbedded:!1})}await r.write(),globalThis.logger.info(`Successfully migrated ${t.length} Scene documents to the latest system data model.`)}}function isHexGrid(e){const{HEXODDR:t,HEXEVENQ:r}=CONST.GRID_TYPES,i=e.grid?.type??e.gridType;return i>=t&&i<=r}function migrateV10Fields(e){let t=!1;"grid"in e&&"Object"!==getType(e.grid)&&(e.grid={size:e.grid},t=!0);const r={gridType:"grid.type",gridColor:"grid.color",gridAlpha:"grid.alpha",gridDistance:"grid.distance",gridUnits:"grid.units",img:"background.src",shiftX:"background.offsetX",shiftY:"background.offsetY"};for(const[i,o]of Object.entries(r)){const r=Scene._addDataFieldMigration(e,i,o);t||=r}return t}function migrateLegacyHexFlag(e){const t=isHexGrid(e),r=getProperty(e,"flags.core.legacyHex");return t&&!0!==r&&!getProperty(e,"_stats.coreVersion")?(setProperty(e,"flags.core.legacyHex",!0),!0):!!(!t&&void 0!==r||t&&!1===r)&&(delete source.flags.core.legacyHex,!0)}function migrateHexGridAlpha(e){if(!isHexGrid(e))return!1;const t=Math.clamp(e.grid?.alpha??.2,0,1),r=Number((t*(2-t)).toFixed(4));return r!==e.grid?.alpha&&(setProperty(e,"grid.alpha",r),!0)}function migrateRangesInLegacyHex(e){if(!(isHexGrid(e)&&getProperty(e,"flags.core.legacyHex")))return!1;const t=2*Math.SQRT1_3;let r=!1;if(Array.isArray(e.lights))for(const i of e.lights)"Object"===getType(i)&&"Object"===getType(i.config)&&(i.config.dim>0&&(i.config.dim*=t,r=!0),i.config.bright>0&&(i.config.bright*=t,r=!0));if(Array.isArray(e.sounds))for(const i of e.sounds)"Object"===getType(i)&&i.radius>0&&(i.radius*=t,r=!0);return r}function migrateOverheadTiles(e){let t=!1;const r=e.grid?.distance??game.system.grid.distance,i=e.foregroundElevation??4*r;if(!Array.isArray(e.tiles))return!1;for(const r of e.tiles)"Object"===getType(r)&&(r.overhead?(r.elevation=i,t=!0):(r.roof&&(r.roof=!1,t=!0),getProperty(r,"occlusion.mode")>CONST.OCCLUSION_MODES.NONE&&(setProperty(r,"occlusion.mode",CONST.OCCLUSION_MODES.NONE),t=!0)));return t}function migrateGlobalLightLuminosity(e){return setProperty(e,"environment.globalLight.luminosity",0),!0}
|
||||
1
resources/app/dist/database/documents/setting.mjs
vendored
Normal file
1
resources/app/dist/database/documents/setting.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseSetting from"../../../common/documents/setting.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import{USER_PERMISSIONS,USER_ROLES}from"../../../common/constants.mjs";import{deepClone}from"../../../common/utils/helpers.mjs";export default class Setting extends(ServerDocumentMixin(BaseSetting)){_onCreate(e,t,i){super._onCreate(e,t,i),this.#e(),"core.moduleConfiguration"===this.key&&!1!==t.updateWorld&&game.world?.onUpdateModuleConfiguration(this.value)}_onUpdate(e,t,i){super._onUpdate(e,t,i),this.#e(),"core.moduleConfiguration"===this.key&&!1!==t.updateWorld&&game.world?.onUpdateModuleConfiguration(this.value)}async _preCreate(e,t,i){if(!1===await super._preCreate(e,t,i))return!1;if("core.moduleConfiguration"===this.key&&this.value){const e=deepClone(this.value);this.#t(e),this.updateSource({value:e})}}async _preUpdate(e,t,i){if(!1===await super._preUpdate(e,t,i))return!1;if("core.moduleConfiguration"===this.key&&"value"in e){const t=JSON.parse(e.value);this.#t(t),e.value=JSON.stringify(t)}}#e(){switch(this.key){case"core.permissions":game.permissions=this.value;break;case"core.compendiumConfiguration":game.compendiumConfiguration=this.value;break;case"core.time":game.activity.worldTime=parseInt(this.value)}}#t(e){const t=[...game.world.relationships.requires,...game.world.system.relationships.requires];for(const i of game.world.modules){const s=i.isCompatibleWithSystem(game.world.system),a=t.find((e=>e.id===i.id));s&&a?e[i.id]=!0:s||(e[i.id]=!1)}}static async getValue(e){const t=await this.findOne({key:e});return t?.value}static async set(e,t,i){t="string"==typeof t?t:JSON.stringify(t);let s=await this.find({key:e});return s.length?(s=s.shift(),s.update({value:t},i)):this.create({key:e,value:t},i)}static async getPermissions(){let e=await this.getValue("core.permissions")||{},t=!1;for(let[i,s]of Object.entries(USER_PERMISSIONS))i in e||(e[i]=Object.values(USER_ROLES).reduce(((e,t)=>(s.defaultRole<=t&&e.push(t),e)),[]),t=!0);return t&&await this.set("core.permissions",e),game.permissions=e}static async advanceTime(e,t,i){const s=await this.getValue("core.time")??0,a=await this.set("core.time",s+e);return db.DatabaseBackend.emit(this.documentName,"update",[{_id:a.id,value:a.value}],t,i),a}}
|
||||
1
resources/app/dist/database/documents/table-result.mjs
vendored
Normal file
1
resources/app/dist/database/documents/table-result.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseTableResult from"../../../common/documents/table-result.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class TableResult extends(ServerDocumentMixin(BaseTableResult)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateV10Fields,version:12},{fn:migrateType,version:12}]}function migrateV10Fields(e){const t=TableResult._addDataFieldMigration(e,"collection","documentCollection"),r=TableResult._addDataFieldMigration(e,"resultCollection","documentCollection"),i=TableResult._addDataFieldMigration(e,"resultId","documentId");return t||r||i}function migrateType(e){switch(e.type){case 0:return e.type=CONST.TABLE_RESULT_TYPES.TEXT,!0;case 1:return e.type=CONST.TABLE_RESULT_TYPES.DOCUMENT,!0;case 2:return e.type=CONST.TABLE_RESULT_TYPES.COMPENDIUM,!0}return!1}
|
||||
1
resources/app/dist/database/documents/tile.mjs
vendored
Normal file
1
resources/app/dist/database/documents/tile.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseTile from"../../../common/documents/tile.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import{getType,setProperty}from"../../../common/utils/helpers.mjs";export default class Tile extends(ServerDocumentMixin(BaseTile)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateTextureData,version:12},{fn:migrateOcclusion,version:12}]}function migrateTextureData(e){const t=Tile._addDataFieldMigration(e,"img","texture.src"),i=Tile._addDataFieldMigration(e,"tint","texture.tint");let r=!1;"width"in e&&e.width<0&&(e.width=Math.abs(e.width),setProperty(e,"texture.scaleX",-1),r=!0);let o=!1;return"height"in e&&e.height<0&&(e.height=Math.abs(e.height),setProperty(e,"texture.scaleY",-1),o=!0),t||i||r||o}function migrateOcclusion(e){return"Object"===getType(e.occlusion)&&(2===Number(e.occlusion.mode)&&(e.occlusion.mode=1,e.roof=!0,!0))}
|
||||
1
resources/app/dist/database/documents/token.mjs
vendored
Normal file
1
resources/app/dist/database/documents/token.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseToken from"../../../common/documents/token.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";import{getProperty,getType,hasProperty,setProperty}from"../../../common/utils/helpers.mjs";export default class Token extends(ServerDocumentMixin(BaseToken)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateLightConfig,version:12},{fn:migrateNegativeLightRadius,version:12},{fn:migrateGradualLightToAttenuation,version:12},{fn:migrateTextureData,version:12},{fn:migrateSight,version:12},{fn:migrateActorData,version:12},{fn:migrateZeroAngles,version:12},{fn:migrate2x2HexShape,version:12},{fn:migrateNonsquareTextureFit,version:12},{fn:migrateNullVisionRanges,version:12},{fn:migrateOcclusionRadiusFlag,version:12},{fn:migrateNegativeLuminosity,version:"12.319"},{fn:migrateTokenRing,version:"12.320"}];async _preUpdate(t,e,i){if(!1===await super._preUpdate(t,e,i))return!1;("x"in t||"y"in t||"elevation"in t)&&(e._priorPosition??={},e._priorPosition[this.id]={x:this.x,y:this.y,elevation:this.elevation}),"_regions"in t&&(e._priorRegions??={},e._priorRegions[this.id]=this._regions)}_onUpdate(t,e,i){if(super._onUpdate(t,e,i),"object"==typeof e.worldTime){const{delta:t,...r}=e.worldTime;Number.isNumeric(t)&&db.Setting.advanceTime(t,r,i)}}async loadRelatedDocuments(){this.baseActor=await db.Actor.get(this.actorId),this.delta||await this.recreateActorDelta();for(const t of Object.values(this.delta.collections))t.initialize({full:!0});this.actorLink?this.actor=this.baseActor:this.actor=this.delta?.constructor.applyDelta(this.delta,this.baseActor)}async recreateActorDelta(){return this.updateSource({delta:new db.ActorDelta({_id:this.id},{parent:this}).toObject()}),this.save()}}function migrateLightConfig(t){let e=!1;const i={dimLight:"light.dim",brightLight:"light.bright",lightAngle:"light.angle",lightColor:"light.color",lightAlpha:"light.alpha",lightAnimation:"light.animation"};for(const[r,a]of Object.entries(i)){const i=Token._addDataFieldMigration(t,r,a);e||=i}return e}function migrateNegativeLightRadius(t){const e=t.light;if("Object"!==getType(e))return!1;let i=!1;return e.dim<0&&(e.dim=Math.abs(e.dim),i=!0),e.bright<0&&(e.bright=Math.abs(e.bright),i=!0),!!i&&(e.luminosity=-1*Math.abs(e.luminosity??.5),!0)}function migrateGradualLightToAttenuation(t){const e=t.light;return"Object"===getType(e)&&("gradual"in e&&(e.attenuation=e.gradual?.5:.3,delete e.gradual,!0))}function migrateTextureData(t){const e=Token._addDataFieldMigration(t,"img","texture.src"),i=Token._addDataFieldMigration(t,"tint","texture.tint");let r=!1,a=t.texture?.scaleX??1,n=t.texture?.scaleY??1;"scale"in t&&(a=n=t.scale,setProperty(t,"texture.scaleX",t.scale),setProperty(t,"texture.scaleY",t.scale),delete t.scale,r=!0);const o=Token._addDataFieldMigration(t,"mirrorX","texture.scaleX",(t=>t.mirrorX?-Math.abs(a):Math.abs(a))),s=Token._addDataFieldMigration(t,"mirrorY","texture.scaleY",(t=>t.mirrorY?-Math.abs(n):Math.abs(n)));return e||i||r||o||s}function migrateSight(t){const e=Token._addDataFieldMigration(t,"sightAngle","sight.angle"),i=Token._addDataFieldMigration(t,"vision","sight.enabled");let r=!1;if("dimSight"in t||"brightSight"in t){const e=t.dimSight??0,i=t.brightSight??0,a=Math.max(e,i);setProperty(t,"sight.range",a),delete t.dimSight,delete t.brightSight,setProperty(t,"sight.brightness",i>=e?1:0),r=!0}return e||i||r}function migrateActorData(t){return"actorData"in t&&("delta"in t||(t.delta=t.actorData,t._id&&"Object"===getType(t.delta)&&(t.delta._id=t._id)),delete t.actorData,!0)}function migrateZeroAngles(t){let e=!1;return"Object"===getType(t.light)&&0===t.light.angle&&(t.light.angle=360,e=!0),"Object"===getType(t.sight)&&0===t.sight.angle&&(t.sight.angle=360,e=!0),e}function migrate2x2HexShape(t){2===t.width&&2===t.height&&(t.hexagonalShape=CONST.TOKEN_HEXAGONAL_SHAPES.ELLIPSE_2)}function migrateNonsquareTextureFit(t){let e;const i=t.width||1,r=t.height||1;if(i<r)e="height";else{if(!(i>r))return!1;e="width"}return setProperty(t,"texture.fit",e),!0}function migrateNullVisionRanges(t){let e=!1;if("Object"===getType(t.sight)&&null===t.sight.range&&(t.sight.range=0,e=!0),Array.isArray(t.detectionModes))for(const i of t.detectionModes)"Object"===getType(i)&&null===i.range&&(i.range=0,e=!0);return e}function migrateOcclusionRadiusFlag(t){if(!hasProperty(t,"flags.core.occlusionRadius"))return!1;const e=Number(t.flags.core.occlusionRadius);return Number.isFinite(e)&&e>0&&(t.occludable={radius:e}),delete t.flags.core.occlusionRadius,!0}function migrateTokenRing(t){const e=getProperty(t,"flags.dnd5e.tokenRing");if(!e)return!1;const{enabled:i,colors:r,effects:a,scaleCorrection:n,textures:o}=e;return t.ring={enabled:i,colors:r,effects:a,subject:{}},o&&(t.ring.subject.texture=o.subject),Number.isFinite(n)&&(t.ring.subject.scale=n),delete t.flags.dnd5e.tokenRing,!0}function migrateNegativeLuminosity(t){const e=t.light;return"Object"===getType(e)&&(e.luminosity<0&&(e.luminosity=.5,e.negative=!0,!0))}
|
||||
1
resources/app/dist/database/documents/user.mjs
vendored
Normal file
1
resources/app/dist/database/documents/user.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import ServerDocumentMixin from"../backend/server-document.mjs";import BaseUser from"../../../common/documents/user.mjs";import sessions from"../../sessions.mjs";import{createPassword}from"../../core/auth.mjs";import{USER_ROLES}from"../../../common/constants.mjs";export default class User extends(ServerDocumentMixin(BaseUser)){static _migrationRegistry=[...super._migrationRegistry,{fn:migratePlainTextPassword,version:12}];get sessions(){return sessions.getUserSessions(this.id)}get sockets(){const e=[];for(let s of config.sockets.values())s.userId===this.id&&e.push(s);return e}get isActive(){return!!this.id&&(game.active&&game.activity&&this.id in game.activity.users)}async _preCreate(e,s,t){await this.#e();if(!1===await super._preCreate(e,s,t))return!1;const{hash:r,salt:a}=createPassword(this.password);this.updateSource({password:r,passwordSalt:a})}_onCreate(e,s,t){super._onCreate(e,s,t),game.users.push(this),delete e.password,delete e.passwordSalt}async _preUpdate(e,s,t){await this.#e();if(!1===await super._preUpdate(e,s,t))return!1;if("password"in e){const{hash:s,salt:t}=createPassword(e.password);e.password=s,e.passwordSalt=t,sessions.deactivateUserSession(this.id)}if("role"in e&&(this.role>t.role||e.role>t.role))throw new Error("You are not authorized to perform this role change.");const r=CONST.USER_ROLES.GAMEMASTER;if("role"in e&&e.role<r&&this.role===r&&!game.users.find((e=>e.role===r&&e.id!==this.id)))throw new Error("You may not demote the only Gamemaster user within the World.")}_onUpdate(e,s,t){super._onUpdate(e,s,t),db.User.get(this.id).then((e=>game.users.findSplice((e=>e.id===this.id),e))),delete e.password,delete e.passwordSalt}async _preDelete(e,s){const t=CONST.USER_ROLES.GAMEMASTER;if(this.role===t&&!game.users.find((e=>e.role===t&&e.id!==this.id)))throw new Error("You may not delete the only Gamemaster user within the World.");await super._preDelete(e,s)}_onDelete(e,s){super._onDelete(e,s),game.users.findSplice((e=>e.id===this.id))}async#e(){if((await User.find({name:this.name})).find((e=>e.id!==this.id)))throw new Error(`User names must be unique, the name ${this.name} is already taken`)}static async dump(e={}){const s=await super.dump(e);return s.forEach((e=>{delete e.password,delete e.passwordSalt})),s.sort(this._sort),s}static fromSource(e,{safe:s=!1,...t}={}){const r=super.fromSource(e,t);return s&&r.updateSource({password:"",passwordSalt:""}),r}static async find(e={},s={}){const t=await super.find(e,s);return t.sort(this._sort),t}static async getUsers(){const e=await User.find({}),s=game.activity.users;for(let t of e){if(!t.id)continue;const e=s[t.id]||{};t.viewedScene=e.sceneId||null}return game.users=e}static _sort(e,s){let t=e.role>=USER_ROLES.ASSISTANT?e.role:1,r=s.role>=USER_ROLES.ASSISTANT?s.role:1;return t!==r?r-t:e.name.compare(s.name)}}function migratePlainTextPassword(e){if("passwordSalt"in e)return!1;const{hash:s,salt:t}=createPassword(e.password||"");return e.password=s,e.paswordSalt=t,!0}
|
||||
1
resources/app/dist/database/documents/wall.mjs
vendored
Normal file
1
resources/app/dist/database/documents/wall.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import BaseWall from"../../../common/documents/wall.mjs";import ServerDocumentMixin from"../backend/server-document.mjs";export default class Wall extends(ServerDocumentMixin(BaseWall)){static _migrationRegistry=[...super._migrationRegistry,{fn:migrateSense,version:12},{fn:migrateLimited,version:12}]}function migrateSense(e){return"sense"in e&&("sight"in e||(e.sight=e.sense),"light"in e||(e.light=e.sense),delete e.sense,!0)}function migrateLimited(e){const i={1:CONST.WALL_SENSE_TYPES.NORMAL,2:CONST.WALL_SENSE_TYPES.LIMITED},n=["light","move","sight","sound"];let t=!1;for(const s of n){if(!(s in e))continue;const n=e[s];n in i&&(e[s]=i[n],t=!0)}return t}
|
||||
1
resources/app/dist/database/fields-extensions.mjs
vendored
Normal file
1
resources/app/dist/database/fields-extensions.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import*as fields from"../../common/data/fields.mjs";import LevelDatabase from"./backend/level-database.mjs";import*as sanitization from"./sanitization.mjs";import EmbeddedDeltaMixin from"./backend/embedded-delta.mjs";import{randomID}from"../../common/utils/helpers.mjs";fields.HTMLField.prototype.sanitize=sanitization.sanitizeHTMLField,fields.FilePathField.prototype.sanitize=sanitization.sanitizeFilePathField,fields.EmbeddedCollectionField.prototype.sanitize=sanitization.sanitizeEmbeddedCollectionField,fields.EmbeddedDocumentField.prototype.sanitize=sanitization.sanitizeEmbeddedDocumentField,Object.defineProperty(fields.EmbeddedCollectionDeltaField.prototype,"model",{get(){return EmbeddedDeltaMixin(this.element.implementation)},configurable:!0}),fields.EmbeddedCollectionField.prototype.expandEmbedded=async function(e,t,i,d){const{logger:a}=global,o=LevelDatabase.formatKey(i,this.name),n=d.sublevels[o],l=e[this.name];if(!l)return[];if(!Array.isArray(l))return a.warn(`Did not retrieve ${this.name} collection from ${this.model.documentName} ${e._id} as it was not an Array.`),[];const r=l.map((e=>LevelDatabase.formatKey(t,e))),s=await n.getMany(r),m=await Promise.all(s.reduce(((e,i)=>(i?._id&&e.push(this.model.expandEmbedded(i,{ldb:d,idPrefix:t,sublevelName:o})),e)),[])),b=s.length-m.length;return b&&a.warn(`${b} embedded ${this.name} records in ${this.model.documentName} ${e._id} were undefined and not retrieved from the ${o} sublevel.`),m},fields.EmbeddedCollectionField.prototype._dbWrite=function(e,t,i,d,{writeEmbedded:a=!0,generateIds:o=!1}={}){const{logger:n}=global,l=LevelDatabase.formatKey(d,this.name),r=e[this.name];return r?Array.isArray(r)?r.reduce(((d,r)=>{if(!r._id){if(!o)return n.warn(`Did not write record in ${this.name} collection for ${this.model.documentName} ${e._id} as it had no ID.`),d;r._id=randomID()}return a&&this.model.batchWrite(r,t,{writeEmbedded:a,idPrefix:i,sublevelName:l}),d.push(r._id),d}),[]):(n.warn(`Did not write ${this.name} collection for ${this.model.documentName} ${e._id} as it was not an Array.`),[]):[]},fields.EmbeddedCollectionField.prototype._dbDelete=function(e,t,i,d){const a=LevelDatabase.formatKey(d,this.name),o=e[this.name];for(const e of o??[])this.model.batchDelete(e,t,{idPrefix:i,sublevelName:a})},fields.EmbeddedCollectionField.prototype._dbDeleteBranch=function(e,t,i){const d=e.getEmbeddedCollection(this.name);for(const e of d)e.batchDelete(t,i)},fields.EmbeddedCollectionDeltaField.prototype._dbDeleteBranch=function(e,t,i){const d=e.getEmbeddedCollection(this.name);for(const a of d._source)d.isTombstone(a._id)?d.documentClass.batchDelete(a,t,{idPrefix:e.dbKey,sublevelName:LevelDatabase.formatKey(e.sublevelName,this.name)}):d.get(a._id).batchDelete(t,i)},fields.EmbeddedDocumentField.prototype.expandEmbedded=async function(e,t,i,d){const{logger:a}=global,o=LevelDatabase.formatKey(i,this.name),n=d.sublevels[o];let l=e[this.name];if(!l)return null;l=LevelDatabase.formatKey(t,l);const r=await n.get(l);return r?(await this.model.expandEmbedded(r,{ldb:d,idPrefix:t,sublevelName:o}),r):(a.warn(`Singleton embedded ${this.name} ${l} in ${this.model.documentName} ${e._id} was undefined and not retrieved from the ${o} sublevel.`),null)},fields.EmbeddedDocumentField.prototype._dbWrite=function(e,t,i,d,{writeEmbedded:a=!0,generateIds:o=!1}={}){const{logger:n}=global,l=LevelDatabase.formatKey(d,this.name),r=e[this.name];if(!r)return null;if(!r._id){if(!o)return n.warn(`Did not write record in ${this.name} field for ${this.model.documentName} ${e._id} as it had no ID.`),null;r._id=randomID()}return a&&this.model.batchWrite(r,t,{writeEmbedded:a,idPrefix:i,sublevelName:l}),r._id},fields.EmbeddedDocumentField.prototype._dbDelete=function(e,t,i,d){const a=LevelDatabase.formatKey(d,this.name),o=e[this.name];o&&this.model.batchDelete(o,t,{idPrefix:i,sublevelName:a})};
|
||||
1
resources/app/dist/database/sanitization.mjs
vendored
Normal file
1
resources/app/dist/database/sanitization.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import path from"node:path";import{cleanHTML}from"./validators.mjs";import Files from"../files/files.mjs";import{fromUuid}from"../core/utils.mjs";import{randomID}from"../../common/utils/helpers.mjs";import{EmbeddedCollectionField,EmbeddedDocumentField,FilePathField}from"../../common/data/fields.mjs";export function sanitizeFilePathField(e,{assetPath:t,document:i,documentId:n,fieldPath:o,user:a}={}){if(null==e||""===e)return e;if("string"!=typeof e)return null;if(this.base64)return e;const r=e.match(/^data:([a-z]+)\/([a-z0-9]+);base64,(.*)/);if(!r)return e;if(a&&!a.can("FILES_UPLOAD"))throw new Error(`You lack FILES_UPLOAD permission and may not upload base64 data to the ${this.name} field.`);const[d,s]=r.slice(2,4);n??=i._id;const l=`${[n,...o].filterJoin("-")}.${d}`,m=path.join(t,l);return fs.mkdirSync(t,{recursive:!0}),fs.writeFileSync(m,Buffer.from(s,"base64")),global.logger.info(`Extracted base64 asset: "${m}"`),Files.standardizePath(path.relative(global.paths.data,m))}export async function handleDocumentAssetUpload(e,t){const i=await fromUuid(e),n=i.constructor.extractedAssetPath,o=t.name.split("."),a=o.pop();return t.name=`${o.join(".")}-${randomID()}.${a}`,t.name=i.parent?[i.parent.id,i.collectionName,i.id,t.name].join("-"):t.name,fs.mkdirSync(n,{recursive:!0}),Files.standardizePath(path.relative(global.paths.data,n))}export function sanitizeHTMLField(e,t={}){return null==e||""===e?e:"string"!=typeof e?"":cleanHTML(e)}export function sanitizeEmbeddedCollectionField(e,t={}){const i=t.document?.[t.fieldPath.at(-1)];for(const[n,o]of e.entries()){const a=i?.get(o._id);e[n]=this.model.sanitizeUserInput(o,{...t,document:a,documentId:o._id,fieldPath:t.fieldPath.concat([o._id])})}return e}export function sanitizeEmbeddedDocumentField(e,t={}){const i=t.document?.[t.fieldPath.at(-1)];return this.model.sanitizeUserInput(e,{...t,document:i,documentId:e?._id})}
|
||||
1
resources/app/dist/database/validators.mjs
vendored
Normal file
1
resources/app/dist/database/validators.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{parseFragment,serialize}from"parse5";import sanitizeHTML from"sanitize-html";import{ALLOWED_HTML_ATTRIBUTES,TRUSTED_IFRAME_DOMAINS}from"../../common/constants.mjs";export function cleanHTML(t){const e=parseFragment(t);return sanitizeHTML(serialize(e),{allowedTags:["header","main","section","article","aside","nav","footer","div","address","h1","h2","h3","h4","h5","h6","hr","br","p","blockquote","summary","details","span","code","pre","a","label","abbr","cite","mark","q","ruby","rp","rt","small","time","dfn","sub","sup","strong","em","b","i","u","s","del","ins","ol","ul","li","dl","dd","dt","table","thead","tbody","tfoot","tr","th","td","col","colgroup","form","input","select","option","button","datalist","fieldset","legend","meter","optgroup","progress","textarea","figure","figcaption","caption","img","video","map","area","track","picture","source","audio","iframe"],allowedAttributes:ALLOWED_HTML_ATTRIBUTES,allowedSchemes:["http","https","data","mailto"],transformTags:{"*":sanitizeTooltips,iframe:sanitizeIframes}})}function sanitizeIframes(t,e){const a=URL.parseSafe(e.src),r=a?.hostname;return TRUSTED_IFRAME_DOMAINS.some((t=>r===t||r?.endsWith(`.${t}`)))?delete e.sandbox:e.sandbox="allow-scripts allow-forms",{tagName:"iframe",attribs:e}}function sanitizeTooltips(t,e){return e["data-tooltip"]&&(e["data-tooltip"]=cleanHTML(e["data-tooltip"])),{tagName:t,attribs:e}}export function stripTags(t){return t.replace(/<[^>]+>/g,"")}
|
||||
1
resources/app/dist/files/downloader.mjs
vendored
Normal file
1
resources/app/dist/files/downloader.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import EventEmitter from"node:events";import fs from"node:fs";import path from"node:path";import{fetchWithTimeout}from"../../common/utils/http.mjs";export default class FileDownloader extends EventEmitter{constructor(e,t){super(),this._url=e,this._localPath=t}_url;_localPath;download=()=>new Promise((async e=>{const t=path.dirname(this._localPath);if(!fs.existsSync(t))return this.emit("error",new Error(`Local directory ${t} does not exist!`)),void e();try{fs.accessSync(t,fs.constants.W_OK)}catch(r){return this.emit("error",new Error(`You do not have permission to write content in ${t}: ${r.message}`)),void e()}let r;try{r=await this.#e()}catch(t){return t.message=`An unexpected error occurred when downloading file ${this._url}: ${t.message}`,this.emit("error",t),void e()}this.emit("fetched");const o=fs.createWriteStream(this._localPath);o.on("error",(e=>this.emit("error",e))),o.on("finish",(()=>{o.close(),e()}));const i=Number(r.headers.get("content-length")||1e8);let s=0;for await(const e of r.body)s+=e.length,o.write(e),this.emit("progress",s,i);o.end()}));async#e(){let e;try{e=await fetchWithTimeout(this._url,{referrerPolicy:"no-referrer"})}catch(t){if(406!==t.code)throw t;e=await fetchWithTimeout(this._url,{referrerPolicy:"no-referrer",mode:"same-origin",headers:{"sec-fetch-mode":"same-origin"}})}return e}}
|
||||
1
resources/app/dist/files/files.mjs
vendored
Normal file
1
resources/app/dist/files/files.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/files/local.mjs
vendored
Normal file
1
resources/app/dist/files/local.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import path from"node:path";import{minimatch}from"minimatch";import AbstractFileStorage from"./storage.mjs";import Files from"./files.mjs";import{encodeURL}from"../../common/utils/helpers.mjs";import{MEDIA_MIME_TYPES}from"../../common/constants.mjs";import mime from"mime-types";export default class LocalFileStorage extends AbstractFileStorage{constructor(t,e){super(t),this.root=e}async createDirectory(t,e={}){if(this.root!==paths.data||!e.isAdmin)throw new Error("You may not create directories in this location");const r=path.join(this.root,t),o=path.relative(this.root,r);if(o&&(o.startsWith("..")||path.isAbsolute(o)))throw new Error("You are not permitted to create a subdirectory in this location");return fs.mkdirSync(decodeURIComponent(r)),r}getDirectories(t){return fs.readdirSync(t).reduce(((e,r)=>{const o=path.join(t,r);return this.isDirectory(o)&&e.push(o),e}),[])}async getFiles({target:t=".",extensions:e=[],wildcard:r=!1,isAdmin:o=!1,type:i}={}){const s=t;t=decodeURIComponent(t);let n=path.join(this.root,t);const a=e.map((t=>t.toLowerCase())),c=path.relative(this.root,n);c&&(c.startsWith("..")||path.isAbsolute(c))&&(n=this.root,t="");let h="";(r||fs.existsSync(n)&&!this.isDirectory(n))&&(h=path.basename(t),t=path.dirname(t),n=path.dirname(n));const l=Object.keys(this.configuration);l.sort(((t,e)=>{const r=e.split("/").length-t.split("/").length;return 0!==r?r:""===t?1:""===e?-1:t.compare(e)}));let p=l.find((t=>RegExp(`^${t}/`).test(`${s}/`)))||"";const m=this.configuration[p]||{private:!1,gridSize:null};if(!fs.existsSync(n)||m.private&&!o)throw new Error(`Directory ${t} does not exist or is not accessible in this storage location`);const d={target:encodeURL(t),private:m.private,gridSize:m.gridSize,dirs:[],privateDirs:[],files:[],extensions:e},u=fs.readdirSync(n).map((t=>path.join(n,t))).reduce(((e,s)=>{const n=path.basename(s);if(n.startsWith("."))return e;if(r&&!minimatch(n,h))return e;if(this.isDirectory(s)){const r=encodeURL(path.posix.join(t,n));if(r in this.configuration&&this.configuration[r].private){if(!o)return e;e.privateDirs.push(r)}return e.dirs.push(s),e}if("folder"===i)return e;const c=path.extname(n).toLowerCase();return(a.length&&a.includes(c)||!a.length&&""!==c)&&e.files.push(s),e}),d);return u.dirs=this.toClientPaths(u.dirs,this.root),u.files=this.toClientPaths(u.files,this.root),u}isDirectory(t){let e=null;try{e=fs.lstatSync(t)}catch(t){return!1}if(e.isDirectory())return!0;if(e.isSymbolicLink())try{return fs.statSync(t).isDirectory()}catch(t){return!1}return!1}async upload(t,{target:e,contentType:r,overwrite:o=!1}={}){if(this.root!==paths.data)throw new Error("You may not upload to this location");const i=path.join(this.root,e);if(!fs.existsSync(i))throw new Error(`Target directory ${i} does not exist.`);const s=path.relative(this.root,i);if(s&&(s.startsWith("..")||path.isAbsolute(s)))throw new Error("You may not upload files to this location.");const n=path.join(i,t.name),a=this.toClientPath(n,this.root);if(fs.existsSync(n)){if(!o)throw new Error("You may not upload non-media files which would overwrite an existing file on the server.");const t=r??mime.lookup(n);if(!MEDIA_MIME_TYPES.includes(t))throw new Error(`You may not overwrite an existing file with a MIME type of ${t}.`)}return new Promise((r=>{t.mv(n,(o=>{const i=""!==e?e:"User Data storage";if(o)throw o.message=`Unable to place ${t.name} in ${i}`,o;const s=`${t.name} saved to ${i}`;logger.info(s),r({status:"success",message:s,path:a})}))}))}toClientPath(t,e){return LocalFileStorage.toClientPath(t,e)}static toClientPath(t,e){const r=Files.standardizePath(path.relative(e,t));try{return decodeURIComponent(r)!==r?r:encodeURL(r)}catch(t){return encodeURL(r)}}}
|
||||
1
resources/app/dist/files/s3.mjs
vendored
Normal file
1
resources/app/dist/files/s3.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import path from"node:path";import{S3 as S3Client}from"@aws-sdk/client-s3";import{minimatch}from"minimatch";import AbstractFileStorage from"./storage.mjs";import{encodeURL,mergeObject}from"../../common/utils/helpers.mjs";export default class S3FileStorage extends AbstractFileStorage{constructor(t,e,i){super(t),this.config=e,this.client=new S3Client(e),this.buckets=i}static fromConfig(t,e){if(!e)return null;const i={};let n=null;if(!0===e)logger.info("Configuring AWS credentials using system configuration or environment variables");else if("string"==typeof e){let t=e;if(fs.existsSync(t)||(t=path.join(paths.config,e)),!fs.existsSync(t)){const e=new Error(`The requested AWS config path ${t} does not exist!`);return logger.error(e),null}const s=JSON.parse(fs.readFileSync(t,"utf-8"));mergeObject(i,s),s.buckets instanceof Array&&(n=s.buckets),logger.info(`Configured AWS credentials using ${t}`)}try{return new this(t,i,n)}catch(t){return logger.error(t),null}}async createDirectory(t,{bucket:e}={}){const{logger:i}=global;let n=t;n=path.posix.join(n.slugify({lowercase:!1}),"/");let s=!1;try{s=await this.client.headObject({Bucket:e,Key:n})}catch(t){}if(s)throw new Error(`The S3 key ${n} already exists and cannot be created`);return await this.client.putObject({Bucket:e,Key:n,Body:""}),i.info(`Created subdirectory in S3 s3://${e}/${t}`),t}async getFiles({target:t=".",extensions:e=[],wildcard:i=!1,bucket:n="",isAdmin:s=!1,type:o}={}){const r={target:t=decodeURIComponent(t),private:!1,gridSize:null,dirs:[],privateDirs:[],files:[],extensions:e.map((t=>t.toLowerCase()))};if(!n)return r.dirs=await this.#t(),r;let a="";i&&(a=path.basename(t),t=path.dirname(t)),t&&!t.endsWith("/")&&(t+="/");const c=this.configuration[n]||{},l=Object.keys(c);l.sort(((t,e)=>{const i=e.split("/").length-t.split("/").length;return 0!==i?i:""===t?1:""===e?-1:t.compare(e)}));let u=l.find((e=>RegExp(`^${e}/`).test(t)));const p=u?c[u]:{private:!1,gridSize:null};if(r.private=p.private,p.private&&!s)return r;const{keys:h,prefixes:f}=await this.#e(n,t);for(let t of f){const e=t.endsWith("/")?t.slice(0,-1):t;if(e in c&&c[e].private){if(!s)continue;r.privateDirs.push(e)}r.dirs.push(e)}if("folder"!==o)for(let e of h){let n=path.posix.relative(t,e);""!==n&&(i&&!minimatch(n,a)||r.extensions.length&&!r.extensions.includes(path.extname(e).toLowerCase())||r.files.push(e))}return r.bucket=n,r.files=this.toClientPaths(r.files,n),r}async#t(){if(this.buckets)return this.buckets;const{logger:t}=global;t.info("Searching for allowed buckets for S3 storage.");const e=[],{Buckets:i}=await this.client.listBuckets({});for(const t of i??[]){await this.#i(t.Name)&&e.push(t.Name)}return this.buckets=e}async#i(t){try{return await this.client.headBucket({Bucket:t}),!0}catch(t){return!1}}async#e(t,e){const i={Bucket:t,MaxKeys:1e3,Delimiter:"/"};"."!==e&&(i.Prefix=e);let n=!0;const s=[],o=new Set,r=async t=>{const e=await this.client.listObjectsV2(t);return{keys:(e.Contents||[]).map((t=>t.Key)),prefixes:(e.CommonPrefixes||[]).map((t=>t.Prefix)),nextToken:e.IsTruncated?e.NextContinuationToken:null}};for(;n;){const t=await r(i);s.push(...t.keys);for(let e of t.prefixes)o.add(e);t.nextToken&&(i.ContinuationToken=t.nextToken),null===t.nextToken&&(n=!1)}return{keys:s,prefixes:o}}async upload(t,{bucket:e,contentType:i,target:n}={}){const s=path.posix.join(n,t.name);let o;try{o=await this.client.putObject({Bucket:e,Key:s,Body:t.data,ContentType:i||"text/plain",ACL:"public-read"})}catch(t){throw t.message=`File upload failed: ${t.message}`,t}const r=this.toClientPath(s,e),a=`Uploaded file ${o.name} to ${r}`;return logger.info(a),{status:"success",message:a,path:r}}#n;get endpoint(){return this.#n}async identifyEndpoint(){const[t]=await this.#t();if(!t)return;const e="extractEndpointMiddleware";this.client.middlewareStack.add((e=>i=>(this.#s(i.request,t),e(i))),{step:"build",name:e});try{await this.client.headBucket({Bucket:t})}finally{this.client.middlewareStack.remove(e)}}#s(t,e){let{hostname:i,port:n,protocol:s,path:o}=t;this.config.forcePathStyle?o=o.replace(`/${e}/`,"/"):i=i.replace(new RegExp(`^${e}\\.`),"");let r=i;Number.isFinite(n)&&(r+=`:${n}`);const[a]=o.split("?"),c=`${s}//${r}${o}`;this.#n={host:r,hostname:i,port:n,protocol:s,path:o,pathname:a,href:c}}toClientPath(t,e){if(!this.#n)throw new Error("Unable to determine S3 client path with no available endpoint.");const{host:i,protocol:n,pathname:s}=this.endpoint;return this.config.forcePathStyle?`${n}//${i}/${e}${s}${encodeURL(t)}`:`${n}//${e}.${i}${s}${encodeURL(t)}`}}
|
||||
1
resources/app/dist/files/storage.mjs
vendored
Normal file
1
resources/app/dist/files/storage.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import path from"node:path";export default class AbstractFileStorage{constructor(t,...e){this.storageName=t}get configuration(){const t=config.files.configuration;return this.storageName in t||(t[this.storageName]={}),t[this.storageName]}async createDirectory(t,e={}){throw new Error("Not Implemented")}async getFiles({target:t,extensions:e,wildcard:r,bucket:o,type:a}={}){throw new Error("Not Implemented")}async upload(t,e={}){throw new Error("Not Implemented")}toClientPaths(t,e){return t.map((t=>this.toClientPath(t,e)))}toClientPath(t,e){throw new Error("Not Implemented")}}
|
||||
1
resources/app/dist/init.mjs
vendored
Normal file
1
resources/app/dist/init.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/interface/electron.js
vendored
Normal file
1
resources/app/dist/interface/electron.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
const path=require("path"),electron=require("electron");class ElectronWindow{constructor(e){this.app=electron.app;const t=process.versions;logger.info(`Running Electron application with version ${t.electron} using Chromium ${t.chrome}`),"win32"===process.platform&&this._defineUserTasks(this.app),"darwin"===process.platform&&(this._defineDockMenu(this.app),this.#e()),this.window=this.create(e),this.app.on("window-all-closed",(()=>{"darwin"===process.platform?this._docked=!0:this.app.quit()})),this.app.on("activate",(()=>{this.window||(this.window=this.create(e)),this._docked&&global.express&&this.initialize(global.express.address),this._docked=!1})),this.app.commandLine.appendSwitch("ignore-certificate-errors"),this.app.on("certificate-error",((e,t,o,r,s,l)=>{/localhost/.test(o)?(e.preventDefault(),l(!0)):l(!1)})),this.window.webContents.setWindowOpenHandler(this._onOpenWindow.bind(this))}initialize(e){if(config.options.proxySSL&&!config.options.isSSL){const e="You may not use proxySSL with the Electron application; SSL certificates must be provided directly.";logger.error(e),global.startupMessages.push({level:"error",message:e})}const t=()=>this.load(e);this.app.isReady()?t():this.app.on("ready",t)}static get defaultOptions(){const e="win32"===process.platform?"fvtt.ico":"vtt-512.png";return{width:1600,height:900,icon:path.join(global.paths.public,"icons",e),backgroundColor:"#2e2c29",minWidth:720,minHeight:460,fullscreen:!0,resizable:!0,show:!0,webPreferences:{autoplayPolicy:"no-user-gesture-required",contextIsolation:!0,nodeIntegration:!1,sandbox:!0}}}create(e){const t=this.constructor.defaultOptions,o=new electron.BrowserWindow(t);return o.webContents.session.setSpellCheckerEnabled(!1),this._createMenu(o),!1===e.fullscreen&&(o.setFullScreen(!1),o.maximize()),o.on("closed",(()=>this.window=null)),o}_createMenu(e){const t={label:"Application",submenu:[{label:"Reload",accelerator:"darwin"===process.platform?"Cmd+R":"F5",role:"reload"},{label:"Force Reload",accelerator:"darwin"===process.platform?"Ctrl+Cmd+R":"Ctrl+F5",role:"forceReload"},{label:"Toggle Fullscreen",accelerator:"darwin"===process.platform?"Ctrl+Cmd+F":"F11",role:"togglefullscreen"},{label:"Toggle Developer Tools",accelerator:"darwin"===process.platform?"Alt+Cmd+I":"F12",role:"toggleDevTools"},{label:"Quit",accelerator:"Command+Q",click:()=>this.app.quit()}]};"darwin"===process.platform&&t.submenu.splice(2,0,{label:"Hide",accelerator:"Cmd+H",role:"hide"});const o=electron.Menu.buildFromTemplate([t,{label:"Edit",submenu:[{label:"Undo",accelerator:"CmdOrCtrl+Z",role:"undo"},{label:"Redo",accelerator:"Shift+CmdOrCtrl+Z",role:"redo"},{label:"Cut",accelerator:"CmdOrCtrl+X",role:"cut"},{label:"Copy",accelerator:"CmdOrCtrl+C",role:"copy"},{label:"Paste",accelerator:"CmdOrCtrl+V",role:"paste"},{label:"Select All",accelerator:"CmdOrCtrl+A",role:"selectAll"}]}]);electron.Menu.setApplicationMenu(o),e.setMenuBarVisibility(!1)}_defineDockMenu(e){const{paths:t}=global,{shell:o,Menu:r}=electron;e.dock.setMenu(r.buildFromTemplate([{label:"Browse User Data",click:()=>o.showItemInFolder(path.join(t.user,"Data"))},{label:"Browse Application Data",click:()=>o.showItemInFolder(path.join(t.root,"public"))}]))}#e(){electron.systemPreferences.askForMediaAccess("microphone"),electron.systemPreferences.askForMediaAccess("camera")}_defineUserTasks(e){const t=global.paths;e.setUserTasks([{program:"explorer.exe",arguments:t.user.replace(new RegExp(path.posix.sep,"g"),path.sep),iconPath:process.execPath,iconIndex:0,title:"Browse User Data",description:"Open User Data Directory"},{program:"explorer.exe",arguments:t.root.replace(new RegExp(path.posix.sep,"g"),path.sep),iconPath:process.execPath,iconIndex:0,title:"Browse Application Data",description:"Open Application Installation Directory"}])}load(e){this.window.loadURL(e).then((()=>this.window.focus()))}quit(){this.app.exit(0)}_onOpenWindow({url:e}={}){return/https?:\/\//.test(e)&&setImmediate((()=>electron.shell.openExternal(e))),{action:"deny"}}}module.exports=ElectronWindow;
|
||||
1
resources/app/dist/logging.mjs
vendored
Normal file
1
resources/app/dist/logging.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import path from"node:path";import winston from"winston";import"winston-daily-rotate-file";export const Logger=winston.Logger;export const LogEntry=winston.LogEntry;export function createLogger(e,o,t){const{createLogger:r,format:n,transports:i}=winston,s=t.logsize||"10m",a=Number(t.maxlogs)||5,l=r({level:"debug",format:n.combine(n.timestamp({format:"YYYY-MM-DD HH:mm:ss"}),n.errors({stack:!0}),n.json()),transports:[new i.DailyRotateFile({filename:path.join(e.logs,"debug"),extension:".log",maxSize:s,maxFiles:a}),new i.DailyRotateFile({filename:path.join(e.logs,"error"),extension:".log",level:"error",maxSize:s,maxFiles:a}),new i.Console({format:n.combine(n.colorize(),n.printf((e=>{let o=`${vtt} | ${e.timestamp} | [${e.level}] ${e.message}`;return e.stack&&(o+="\n"+e.stack),o})))})]});for(let e of o)l.log(e);return l}
|
||||
1
resources/app/dist/migrations.mjs
vendored
Normal file
1
resources/app/dist/migrations.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import path from"node:path";import fs from"node:fs";import NeDB from"nedb";import{DOCUMENT_OWNERSHIP_LEVELS}from"../common/constants.mjs";import Files from"./files/files.mjs";async function migrateDeleteFOW(e){const a=path.join(e.path,"data","fog.db");fs.existsSync(a)&&(global.logger.info("Deleting legacy FogExploration database."),fs.unlinkSync(a))}async function migrateSceneDefaultPermission(e){const a=path.join(e.path,"data","scenes.db");if(!fs.existsSync(a))return;const t=await new Promise(((e,t)=>{const n=new NeDB(a);n.loadDatabase((a=>a?t(a):e(n)))})),n=await new Promise(((e,a)=>{t.find({},((t,n)=>t?a(t):e(n)))}));for(const e of n){const a=e.permission?.default;a===DOCUMENT_OWNERSHIP_LEVELS.OWNER&&(global.logger.info(`Migrating default ownership of Scene record [${e._id}] from OWNER to OBSERVER.`),await new Promise(((a,n)=>{t.update({_id:e._id},{$set:{"permission.default":DOCUMENT_OWNERSHIP_LEVELS.OBSERVER}},{},(e=>e?n(e):a()))})))}}async function migrateChatMessages(e){const a=path.join(e.path,"data","chat.db"),t=path.join(e.path,"data","messages.db"),n=fs.statSync(t,{throwIfNoEntry:!1});if(n&&!(n.size>0)&&fs.existsSync(a)){global.logger.info(`Migrating ChatMessage database. Copying '${a}' -> '${t}'`);try{await Files.copyLargeFile(a,t)}catch(e){return void global.logger.error(`Migration failed: ${e.message}`)}fs.unlinkSync(a)}}async function migrateDeleteDrawings(e){const a=path.join(e.path,"data","drawings"),t=path.join(e.path,"data","drawings.db"),n=fs.existsSync(a),s=fs.existsSync(t);(n||s)&&global.logger.info("Deleting unused Drawing database."),n&&fs.rmSync(a,{recursive:!0}),s&&fs.unlinkSync(t)}export default{"0.7.3":[migrateDeleteFOW],"0.8.7":[migrateSceneDefaultPermission],9.224:[migrateChatMessages],12:[migrateDeleteDrawings]};
|
||||
1
resources/app/dist/packages/_module.mjs
vendored
Normal file
1
resources/app/dist/packages/_module.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export{default as ServerPackageMixin}from"./package.mjs";import{default as PackageWarnings}from"./warnings.mjs";import{default as PackageBackups}from"./package-backups.mjs";import Module from"./module.mjs";import System from"./system.mjs";import World from"./world.mjs";import HotReload from"./hot-reload.mjs";export{Module,System,World,HotReload};export{default as PreviewCompatibility}from"./preview-compatibility.mjs";export const PACKAGE_TYPE_MAPPING={module:Module,system:System,world:World};export const warnings=new PackageWarnings;export const backups=new PackageBackups;
|
||||
1
resources/app/dist/packages/hot-reload.mjs
vendored
Normal file
1
resources/app/dist/packages/hot-reload.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import path from"node:path";import fs from"node:fs";import*as chokidar from"chokidar";import Files from"../files/files.mjs";export default class HotReload{static#a=new Map;static async watchForHotReload(a,t){if(await HotReload.stopWatching(),global.config.options.hotReload){global.express.debug&&await HotReload.#t({id:"core"}),a.system.flags.hotReload&&await HotReload.#t(a.system);for(const o of a.modules.filter((a=>a.flags.hotReload&&t[a.id])))await HotReload.#t(o)}}static async stopWatching(){for(let a of HotReload.#a.values())await a.close();HotReload.#a.clear()}static async#t(a){const t=new Set([".js",".mjs",".css",".html",".hbs",".json"]);let o="core"===a.id?t:new Set(a.flags.hotReload.extensions);if(o=new Set([...o].map((a=>a.startsWith(".")?a:`.${a}`))),!o.size)return void console.warn(`Hot Reload disabled for "${a.id}" because it has no extensions defined.`);const e="core"===a.id?global.paths.public:a.path;let s=[];if("core"!==a.id&&a.flags.hotReload.paths)for(const t of a.flags.hotReload.paths){const o=Files.standardizePath(path.join(e,t));Files.isPathContained(o,e)?s.push(o):console.warn(`Hot Reload skipped for "${a.id}" path "${o}" because it is not contained within the package directory.`)}0===s.length&&(s=[e]);const i=chokidar.watch(s);console.log(`Watching for changes to ${a.id} in ${s.join(", ")}`),i.on("change",(async t=>{const e=path.extname(t);if(!o.has(e))return;const s=fs.readFileSync(t,{encoding:"utf8"}),i=t.replace(path.normalize(global.paths.data),"").replace(path.normalize(global.paths.public),""),n=Files.standardizePath(i),l={packageType:a.type,packageId:a.id,content:s,path:n.startsWith("/")?n.slice(1):n,extension:e.replace(".","")};express.io.emit("hotReload",l)})),HotReload.#a.set(a.id,i)}}
|
||||
1
resources/app/dist/packages/installer.mjs
vendored
Normal file
1
resources/app/dist/packages/installer.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import EventEmitter from"node:events";import path from"node:path";import fs from"node:fs";import Files from"../files/files.mjs";import{PACKAGE_TYPE_MAPPING}from"./_module.mjs";export default class PackageInstaller extends EventEmitter{constructor(t,s,i,e,a){super(),this._type=t,this._baseDir=s,this._id=i,this._localZip=e,this._auth=a}_id;_localZip;_auth;_type;_baseDir;#t={step:null,pct:0};async install(){this.#t={step:null,pct:0};try{return(await this.#s()).vend()}catch(t){this.emit("error",t)}finally{fs.unlinkSync(this._localZip)}}async#s(){if(this._id.startsWith(".."))throw new Error("You are not allowed to install packages outside of the designated directory path");const t=path.join(this._baseDir,this._id);let s="",i=await Files.summarizeArchive(this._localZip,{manifestPath:`${this._type}.json`});if(null===i.manifest){const t=i.contents.find((t=>t.endsWith(`${this._type}.json`)));s=`${path.dirname(t)}/`,i=await Files.summarizeArchive(this._localZip,{manifestPath:`${s}${this._type}.json`})}if(!i.manifest)throw fs.unlinkSync(this._localZip),new Error(`The downloaded package ${this._id} did not contain the expected ${this._type}.json manifest file.`);const e=new(0,PACKAGE_TYPE_MAPPING[this._type])(JSON.parse(i.manifest),{installed:!1});if(e.persistentStorage&&fs.existsSync(path.join(t,"storage"))){const s=fs.readdirSync(t);for(let i of s){if("storage"===i)continue;const s=path.join(t,i);await fs.promises.rm(s,{recursive:!0})}}else await fs.promises.rm(t,{force:!0,recursive:!0});if(await Files.extractArchive(this._localZip,t,{removeRoot:s,onProgress:this.#i.bind(this)}),e.persistentStorage){const s=path.join(t,"storage");fs.existsSync(s)||fs.mkdirSync(s)}if(this._auth){const s=path.join(t,"signature.json"),i="signatureV2"in this._auth?{key:this._auth.key,package:this._id,signature:this._auth.signatureV2}:{key:this._auth.key,signature:this._auth.signature};fs.writeFileSync(s,JSON.stringify(i,null,2))}return e}#i(t,s,i){return this.emit("progress",CONST.SETUP_PACKAGE_PROGRESS.STEPS.INSTALL,s,i)}}
|
||||
1
resources/app/dist/packages/module.mjs
vendored
Normal file
1
resources/app/dist/packages/module.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import path from"node:path";import fs from"node:fs";import{BaseModule}from"../../common/packages/module.mjs";import ServerPackageMixin from"./package.mjs";import Collection from"../../common/utils/collection.mjs";import Files from"../files/files.mjs";export default class Module extends(ServerPackageMixin(BaseModule)){static#e;static getPackages({system:e,coreTranslation:t=!1,enforceCompatibility:s=!1}={}){return super.getPackages({enforceCompatibility:s}).reduce(((s,i)=>(t&&!i.providesCoreTranslation||e&&!i.supportsSystem(e)||s.set(i.id,i),s)),new Collection)}static getCoreTranslationOptions(){const e=this.getPackages({coreTranslation:!0}).reduce(((e,t)=>{if(t.incompatibleWithCoreVersion)return e;let s=new Set;for(let i of t.languages){const a=e[i.lang]=e[i.lang]||{id:i.lang,label:"",modules:[]};a.label||(a.label=i.name),s.has(i.lang)||a.modules.push({id:t.id,label:t.title,path:i.path}),s.add(i.lang)}return e}),{en:{id:"en",label:"English",modules:[{id:"core",label:"Default",path:"lang/en.json"}]}});let t=[e.en];return e.en.modules=e.en.modules.slice(0,1),delete e.en,t.concat(Object.values(e))}static getCoreTranslationStyles(){if(!this.#e){const e=(config.options.language??"en.core").split(".")[1],t=this.getPackages({coreTranslation:!0}).get(e);this.#e=t?.styles||[]}return this.#e}get providesCoreTranslation(){return this.coreTranslation&&this.languages.size}isCompatibleWithSystem(e){if(!this.relationships?.systems?.size)return!0;const t=this.relationships.systems.find((t=>t.id===e.id));return!!t&&this.constructor.testDependencyCompatibility(t.compatibility,e)}supportsSystem(e){return!this.relationships?.systems?.size||!!this.relationships.systems.find((t=>t.id===e.id))}static create(e){if(e.id=e.id.slugify({strict:!0}),e.id.startsWith(".."))throw new Error("You are not allowed to create a Module outside of the modules directory path.");const t=path.join(this.baseDir,e.id);if(fs.existsSync(t))throw new Error(`A Module already exists in the requested directory ${e.id}.`);const s=new Module(e,{installed:!0});return fs.mkdirSync(t),s.save(),Files.writeFileSyncSafe(path.join(t,".gitattributes"),"packs/** binary\n"),globalThis.logger.info(`Created Module "${s.id}"`),this.packages&&this.packages.set(s.id,s),s.vend()}updateManifest(e){return this.updateSource(e),this.save(),this.vend()}static createOrUpdate(e){const t=this.get(e.id);return t?t.updateManifest(e):this.create(e)}}
|
||||
1
resources/app/dist/packages/package-backups.mjs
vendored
Normal file
1
resources/app/dist/packages/package-backups.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/packages/package.mjs
vendored
Normal file
1
resources/app/dist/packages/package.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/packages/preview-compatibility.mjs
vendored
Normal file
1
resources/app/dist/packages/preview-compatibility.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import Module from"./module.mjs";import System from"./system.mjs";import World from"./world.mjs";import{ReleaseData}from"../../common/config.mjs";import Collection from"../../common/utils/collection.mjs";import{PACKAGE_AVAILABILITY_CODES}from"../../common/constants.mjs";export default class PreviewCompatibility{constructor(e){if(!(e instanceof ReleaseData))throw new Error("You must provide an instance of ReleaseData to the PreviewCompatibility constructor.");if(e.version===globalThis.release.version)throw new Error("You may not preview compatibility for the current software release.");this.release=e,this.release.maxGeneration=globalThis.release.maxGeneration,this.release.maxStableGeneration=globalThis.release.maxStableGeneration}release;modules=new Collection;systems=new Collection;worlds=new Collection;async evaluate(){await this.getRepositoryPackages(),await this.testCompatibility();for(const e of this.modules.keys())Module.packages.has(e)||this.modules.delete(e)}async getRepositoryPackages(){await this.#e(System,this.systems,{installedOnly:!0}),await this.#e(Module,this.modules,{installedOnly:!1}),this.worlds=packages.World.packages.reduce(((e,s)=>{const t=s.clone();return t.system=this.systems.get(s._source.system),t.compatibility.maximum=t._source.compatibility.maximum,e.set(t.id,t)}),new Collection)}async testCompatibility(){const e=PACKAGE_AVAILABILITY_CODES,{release:s,modules:t,systems:a,worlds:i}=this;for(const e of t.values())e.availability=Module.testAvailability(e,{release:s});for(const e of a.values())e.availability=System.testAvailability(e,{release:s});for(const o of i.values())o.availability=World.testAvailability(o,{release:s,modules:t,systems:a,systemAvailabilityThreshold:e.UNVERIFIED_GENERATION});for(const s of System.packages.keys()){const i=a.get(s);await i._testRequiredDependencies(t)||(i.availability=e.MISSING_DEPENDENCY)}for(const s of Module.packages.keys()){const i=t.get(s);await i._testRequiredDependencies(t)||(i.availability=e.MISSING_DEPENDENCY);await i._testSupportedSystems(a)||(i.availability=e.UNVERIFIED_SYSTEM)}for(const s of World.packages.keys()){const a=i.get(s);await a._testRequiredDependencies(t)||(a.availability=e.MISSING_DEPENDENCY)}}async#e(e,s,{installedOnly:t=!0}={}){s.clear();const a=e.getPackages(),i=await e.getRepositoryPackages(),o=await e.getRepositoryPackages({release:this.release});for(const e of o.packages.values())t&&!a.has(e.id)||s.set(e.id,e);for(const e of i.packages.values())t&&!a.has(e.id)||s.has(e.id)||s.set(e.id,e);let l=[];for(const t of a.values())s.has(t.id)||(l.length>=8&&(await Promise.all(l),l=[]),l.push(e.fromRemoteManifest(t.manifest).then((e=>{s.set(t.id,e)})).catch((()=>{s.set(t.id,t.clone())}))));await Promise.all(l)}vend(){return{version:this.release.version,world:this.worlds.map((e=>e.vend())),system:this.systems.map((e=>e.vend())),module:this.modules.map((e=>e.vend()))}}static async test(e={}){const s=new this(new ReleaseData({channel:"stable",node_version:16,...e}));return await s.evaluate(),s}}
|
||||
1
resources/app/dist/packages/system.mjs
vendored
Normal file
1
resources/app/dist/packages/system.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import path from"node:path";import{BaseSystem}from"../../common/packages/module.mjs";import{ALL_DOCUMENT_TYPES,PACKAGE_AVAILABILITY_CODES}from"../../common/constants.mjs";import{isNewerVersion}from"../../common/utils/helpers.mjs";import ServerPackageMixin,{PackageAssetField}from"./package.mjs";export default class System extends(ServerPackageMixin(BaseSystem)){static defineSchema(){const e=super.defineSchema();return e.background=new PackageAssetField({relativeToPackage:!1,mustExist:!1,...e.background.options}),e}#e;#t=0;get template(){return this._template}_template;_initialize(e){super._initialize(e),this._initializeTemplateDocumentTypes()}_initializeTemplateDocumentTypes(){if(this.loadDataTemplate(),!this.installed||!this._template)return;const e=["htmlFields","filePathFields","gmOnlyFields"];for(const[t,s]of Object.entries(this._template))for(const a of s.types||[]){const i=this.documentTypes[t]||={};i[a]={};for(const t of e)t in s&&(i[a][t]=s[t])}}validate(e){return this.installed&&this.loadDataTemplate(this._source.id),super.validate(e)}updateSource(e={},t={}){return this._template=void 0,super.updateSource(e,t)}vend(){const e=super.vend();return e.strictDataCleaning=this._template?.strictDataCleaning,e}loadDataTemplate(e=this.id){return this._template||(this._template=System.#s(e)),this._template}static#s(e){const t=path.join(this.baseDir,e,"template.json");if(!fs.existsSync(t))return null;let s;try{s=JSON.parse(fs.readFileSync(t,"utf-8"))}catch(t){throw new Error(`Unable to parse system template for ${e}: ${t.message}`)}for(const t of ALL_DOCUMENT_TYPES){const a=s[t];if(!a)continue;const i=getDocumentClass(t);a.types=(a.types||[]).filter((a=>{try{return System.#a(e,i,a),a}catch(i){packages.warnings.add(e,{type:this.type,level:"warning",message:i.message}),console.warn(i.message),delete s[t][a]}}))}return s}static#a(e,t,s){const a=t.documentName;if(t.metadata.coreTypes.includes(s))throw new Error(`System "${e}" defines ${a} document sub-type "${s}" which is a reserved core type.`);if(s.includes("."))throw new Error(`System "${e}" defines ${a} document sub-type "${s}" which may not contain periods.`)}async getUpdateNotification(){const e=Date.now();if(!this.#e||e-this.#t>864e5){const e=await this.constructor.check(this.manifest,this);e.isUpgrade?globalThis.logger.info(`${this.title} update ${e.remote.version} is now available!`):globalThis.logger.info(`No system update for ${this.title} is currently available.`),this.#e={hasUpdate:e.isUpgrade,version:e.remote?.version||null}}return this.#e}async checkUpdateAvailable(){const e=await System.fromRemoteManifest(this.manifest);return isNewerVersion(e.version,this.version)&&e.availability<=PACKAGE_AVAILABILITY_CODES.UNVERIFIED_GENERATION?e.version:null}static async install(e,t,s,a,i){const n=await super.install(e,t,s,a,i);return packages.World.resetPackages(),n}static async uninstall(e){const t=await super.uninstall(e);return packages.World.resetPackages(),t}}
|
||||
1
resources/app/dist/packages/views.mjs
vendored
Normal file
1
resources/app/dist/packages/views.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/packages/warnings.mjs
vendored
Normal file
1
resources/app/dist/packages/warnings.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default class PackageWarnings extends Map{add(r,{type:e,level:t="error",message:n=""}={}){this.has(r)||this.set(r,{id:r,type:e,warning:new Set,error:new Set});const s=this.get(r);e&&!s.type&&(s.type=e),s[t].add(n)}toJSON(){const r={};for(const[e,t]of this.entries())r[e]={...t,warning:Array.from(t.warning),error:Array.from(t.error)};return r}}
|
||||
1
resources/app/dist/packages/world.mjs
vendored
Normal file
1
resources/app/dist/packages/world.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/paths.mjs
vendored
Normal file
1
resources/app/dist/paths.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fs from"node:fs";import module from"node:module";import os from"node:os";import path from"node:path";export default function configurePaths({root:t,messages:a,debug:o}={}){const e=path.dirname(t),s="resources"===path.basename(e)?path.dirname(e):t;process.env.NODE_PATH=t,module.Module._initPaths();const n=_getEnvDataPath(),i=path.join(n,"Config"),r=path.join(i,"options.json"),p=path.join(t,o?"server":"dist");module.paths=[path.join(t,"node_modules"),p];const h={root:t,app:s,public:path.join(t,"public"),code:p,common:path.join(t,"common"),envData:n,envConfig:i,envOptions:r};let c;for(const t of _userDataPaths(h))try{c=_validateUserDataPath(t,h);break}catch(t){a.push({level:"error",message:t.message})}if(!c)throw new Error("No valid user data path was available. Check your application config.");Object.assign(h,{user:c,data:path.join(c,"Data"),config:path.join(c,"Config"),options:path.join(c,"Config","options.json"),logs:path.join(c,"Logs"),backups:path.join(c,"Backups")});for(let[t,a]of Object.entries(h))h[t]=path.posix.normalize(a.split(path.sep).join(path.posix.sep));return h}function*_userDataPaths(t){const a=process.argv.find((t=>t.startsWith("--dataPath")));if(a&&(yield path.resolve(a.split("=")[1])),"FOUNDRY_VTT_DATA_PATH"in process.env&&(yield path.resolve(process.env.FOUNDRY_VTT_DATA_PATH)),fs.existsSync(t.envOptions)){const a=JSON.parse(fs.readFileSync(t.envOptions,"utf8"));a.dataPath&&(yield a.dataPath)}yield t.envData}function _validateUserDataPath(t,a){const o=path.dirname(t);if(!fs.existsSync(o))throw new Error(`The requested data path parent directory "${o}" does not exist.`);const e=path.relative(a.app,t);if(!e.startsWith("..")&&!path.isAbsolute(e))throw new Error(`The data path "${t}" must not be inside the application location "${a.app}".`);return t}function _getEnvDataPath(){const t=os.homedir(),a="FoundryVTT";switch(process.platform){case"win32":return path.join(process.env.LOCALAPPDATA||path.join(t,"AppData","Local"),a);case"darwin":return path.join(t,"Library","Application Support",a);default:let o=process.env.XDG_DATA_HOME||path.join(t,".local","share");if(!fs.existsSync(o))try{fs.mkdirSync(o,{recursive:!0})}catch(t){o="/local"}return path.join(o,a)}}
|
||||
1
resources/app/dist/server/error.mjs
vendored
Normal file
1
resources/app/dist/server/error.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default class ServerError extends Error{constructor(s,r){super(s),r&&(this.stack=r)}toJSON(){return{class:this.constructor.name,message:this.message,stack:this.stack}}}
|
||||
1
resources/app/dist/server/express.mjs
vendored
Normal file
1
resources/app/dist/server/express.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/server/sockets.mjs
vendored
Normal file
1
resources/app/dist/server/sockets.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import ServerError from"./error.mjs";import Activity from"../components/activity.mjs";import Files from"../files/files.mjs";import sessions from"../sessions.mjs";import ProseMirrorAuthority from"../components/prosemirror.mjs";import Document from"../../common/abstract/document.mjs";import{isSubclass}from"../../common/utils/helpers.mjs";export async function handleEvent(e,t,o,s){const r=this.user;if(!r)throw new Error(`Unrecognized User attacked to socket ${this.id} for event ${e}`);let i;o.options=o.options||{};let n={userId:r?.id,request:o};try{n.result=await t(r,o),i=o.broadcast??!0}catch(e){e=new ServerError(e.message,e.stack),logger.error(e),n.error=e,i=!1}return s?(s(n),i&&this.broadcast.emit(e,n)):i&&this.server.emit(e,n),n}export function handleCustomSocket(e,t,{recipients:o}={},s){if(o instanceof Array)for(let s of o){const o=game.users.find((e=>e.id===s));if(o)for(let s of o.sockets)s.emit(e,t,this.user.id)}else this.broadcast.emit(e,t,this.user.id);s instanceof Function&&s()}export function handleMigrateDocumentData(e,t,o){const s=global.db[e];if(isSubclass(s,Document))if("object"==typeof t)try{o({source:s.fromImport(t).toObject()})}catch(e){o({error:e.message})}else o({error:'Invalid Document data provided to "migrateDocumentData" operation'});else o({error:`Invalid Document name "${e}" provided to "migrateDocumentData" operation`})}export async function handleConfirmTeleportToken({behaviorUuid:e,tokenUuid:t,userId:o},s){if(!this.user.isGM)return s(!1);const r=game.users.find((e=>e.id===o));if(!r)return s(!1);s(await new Promise((o=>r.sockets.forEach((s=>s.emit("confirmTeleportToken",{behaviorUuid:e,tokenUuid:t},o))))))}export async function activate(e,t){const o=e.handshake.query,s={sessionId:null,userId:null},r=sessions.sessions.get(o.session);if(!r)return e.emit("session",null);if(e.session=r,s.sessionId=r.id,game.world&&game.ready){const t=r.worlds[game.world.id],o=game.users.find((e=>e.id===t));o?(e.user=o,e.userId=s.userId=o.id):delete r.worlds[game.world.id]}e.emit("session",s);for(const o of t)o.socket&&e.on(o.socket,(e=>o.handleSocket(r,e)));e.on("getWorldStatus",requestWorldState),Files.socketListeners(e,handleEvent),s.userId&&(Activity.socketListeners(e),e.on("chatBubble",handleCustomSocket.bind(e,"chatBubble")),e.on("av",handleCustomSocket.bind(e,"av")),ProseMirrorAuthority.socketListeners(e),db.DatabaseBackend.socketListeners(e),db.Scene.socketListeners(e),db.JournalEntry.socketListeners(e),db.Playlist.socketListeners(e),db.FogExploration.socketListeners(e),db.Actor.socketListeners(e),db.Region.socketListeners(e),packages.World.socketListeners(e,handleEvent),e.on("migrateDocumentData",handleMigrateDocumentData),e.on("confirmTeleportToken",handleConfirmTeleportToken.bind(e)))}function requestWorldState(e){return e(game.world&&game.ready)}
|
||||
1
resources/app/dist/server/upnp.mjs
vendored
Normal file
1
resources/app/dist/server/upnp.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import natupnp from"nat-upnp";export default class UPnP{constructor({port:t=3e4,ttl:e}={}){this.port=t,this.ttl=e||600}client=natupnp.createClient();#t;createMapping(){global.logger.info(`Requesting UPnP port forwarding to destination port ${this.port}`);try{this.#e()}catch(t){return t.message=`Failed to created requested UPnP mapping: ${t.message}`,global.logger.error(t),null}return this.#t=setInterval(this.#e.bind(this),1e3*this.ttl/2),this}#e(){config.options?.debug&&global.logger.debug(`Renewing UPnP port forwarding lease to destination port ${this.port}`),this.client.portMapping({public:this.port,private:this.port,ttl:this.ttl,description:"Foundry Virtual Tabletop"})}removeMapping(){this.#t&&(clearInterval(this.#t),this.#t=void 0),this.client.portUnmapping({public:this.port})}}
|
||||
1
resources/app/dist/server/views/api.mjs
vendored
Normal file
1
resources/app/dist/server/views/api.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import View from"./view.mjs";import*as util from"../../../common/utils/helpers.mjs";export default class APIView extends View{route="/api/status";_methods=["get"];async handleGet(e,s){const{game:t,release:i}=globalThis,r={active:!1,version:i?.version};t.ready&&util.mergeObject(r,{active:!0,world:t.world.id,system:t.system.id,systemVersion:t.system.version,users:Object.values(t.activity.users).length,uptime:t.activity.serverTime}),s.json(r)}}
|
||||
1
resources/app/dist/server/views/auth.mjs
vendored
Normal file
1
resources/app/dist/server/views/auth.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import Express from"../express.mjs";import sessions from"../../sessions.mjs";import*as util from"../../../common/utils/helpers.mjs";import{Module}from"../../packages/_module.mjs";import View from"./view.mjs";export default class AuthView extends View{route="/auth";socket="getAuthData";_template="auth";_methods=["get","post"];async handleGet(e,s){if(config.license.needsSignature)return s.redirect(`${e.baseUrl}/license`);if(global.game.world)return s.redirect(`${e.baseUrl}/join`);const t=View._getStaticContent({setup:!0}),o={bodyClass:`auth flexcol theme-${config.options.cssTheme}`,messages:sessions.getMessages(e,{clear:!1}),layout:"setup",scripts:t.scripts,styles:t.styles};s.render(this._template,o)}async handlePost(e,s){const{game:t}=global,o=sessions.authenticateAdmin(e,s),a=!t.world&&o.success;return s.redirect(e.baseUrl+util.getRoute(a?"setup":"auth"))}async handleSocket(e,s){return s({release:global.release,worlds:[],systems:[],modules:Module.getPackages({coreTranslation:!0}).map((e=>e.vend())),options:{language:global.config.options.language}})}}
|
||||
1
resources/app/dist/server/views/error.mjs
vendored
Normal file
1
resources/app/dist/server/views/error.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import View from"./view.mjs";export default class ErrorView extends View{route="/no";_methods=["get"];async handleGet(e,r,...o){View.error(e,r,...o)}}
|
||||
1
resources/app/dist/server/views/game.mjs
vendored
Normal file
1
resources/app/dist/server/views/game.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import View from"./view.mjs";export default class GameView extends View{route="/game";_template="game";_methods=["get"];async handleGet(e,r){const{db:t,game:o,logger:s}=global;if(!o.world)return this._noWorld(e,r);if(!e.user)return r.redirect(`${e.baseUrl}/join`);if(!await t.User.get(e.user))return s.error(Error(`User ${e.user} not found!`)),r.redirect(`${e.baseUrl}/join`);const i=await t.Setting.getValue("core.moduleConfiguration")||{},a=View._getStaticContent({world:!0,moduleConfig:i});r.render(this._template,{scripts:a.scripts,styles:a.styles,watermark:null})}}
|
||||
1
resources/app/dist/server/views/join.mjs
vendored
Normal file
1
resources/app/dist/server/views/join.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import View from"./view.mjs";import sessions from"../../sessions.mjs";import{Module}from"../../packages/_module.mjs";import{PASSWORD_SAFE_STRING}from"../../../common/constants.mjs";import Express from"../express.mjs";export default class JoinView extends View{route="/join";socket="getJoinData";_template="join";_methods=["get","post"];async handleGet(e,s){const{game:o}=global;if(!o.world)return this._noWorld(e,s);if(!o.ready)return setTimeout((()=>this.handleGet(e,s)),1e3);await sessions.logoutWorld(e,s);const t=o.world.background,a=t?`body.background {\n --background-url: url("${URL.parseSafe(t)?t:foundry.utils.getRoute(t,{prefix:express.routePrefix})}");\n }`:"",n=View._getStaticContent({setup:!0});return s.render(this._template,{bodyClass:["auth","join","flexcol",t?"background":"",`theme-${config.options.cssTheme}`,`join-theme-${o.world.joinTheme??"default"}`].filterJoin(" "),messages:sessions.getMessages(e,{clear:!1}),pageTitle:o.world.title,layout:"setup",scripts:n.scripts,styles:n.styles,inlineStyles:a})}async handlePost(e,s){const{config:o,game:t}=global;if(!t.world)return this._noWorld(e,s);let a={};switch(e.body.action){case"shutdown":if(o.options.demoMode)return s.status(401),s.send("This option is not available for servers running in demo mode"),s;if(!o.adminPassword)return s.status(403),s.send("ERROR.InvalidAdminKey"),s;if(!sessions.authenticateAdmin(e,s).success)return s.send(e.session.messages.pop()?.message),e.session.messages=[],s;a=await t.world.deactivate(e,{asAdmin:!0}),a.status="success",a.message="The game world has been successfully deactivated";break;case"join":if(await sessions.logoutWorld(e,s),a=await sessions.authenticateUser(e,s),"failed"===a.status)return s}return s.json(a)}async handleSocket(e,s){const{game:o}=global;return o.world?s({release:global.release,world:o.world.vend(),modules:Module.getPackages({coreTranslation:!0}).map((e=>e.vend())),passwordString:PASSWORD_SAFE_STRING,isAdmin:e.admin,users:await db.User.dump(),activeUsers:Array.from(Object.keys(o.activity.users)),userId:e.worlds[o.world.id]||null,options:{language:global.config.options.language}}):s({})}}
|
||||
1
resources/app/dist/server/views/license.mjs
vendored
Normal file
1
resources/app/dist/server/views/license.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import View from"./view.mjs";import sessions from"../../sessions.mjs";export default class LicenseView extends View{route="/license";_template="license";_methods=["get","post"];async handleGet(e,s){const t=global.config.license;if(!t.needsSignature)return s.redirect("/");const n=t.license?"eula":"key";return this.#e(e,s,n)}async handlePost(e,s){const{config:t}=global,n=t.license;if(!n.needsSignature)return s.redirect("/");let i=n.license?"eula":"key";if("key"===i)try{n.applyLicense(e.body.licenseKey),sessions.setMessage(e,s,"info","License key entered, please sign the End User License Agreement."),i="eula"}catch(t){sessions.setMessage(e,s,"error",t.message)}else{"decline"in e.body&&(n.applyLicense(null),t.app?t.app.quit():process.exit());const i=await n.sign();if("success"===i.status)return sessions.setMessage(e,s,"info",i.message),s.redirect(`${e.baseUrl}/setup`);if("error"===i.status)return sessions.setMessage(e,s,"error",i.message),n.applyLicense(null),s.redirect(`${e.baseUrl}/license`)}return this.#e(e,s,i)}#e(e,s,t){const n=View._getStaticContent({setup:!0});return s.render(this._template,{layout:"setup",bodyClass:"auth flexcol",scripts:n.scripts,styles:n.styles,isEULA:"eula"===t,licenseKey:e.body.licenseKey,messages:sessions.getMessages(e,{clear:!1}),step:t})}}
|
||||
1
resources/app/dist/server/views/players.mjs
vendored
Normal file
1
resources/app/dist/server/views/players.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import sessions from"../../sessions.mjs";import{Module}from"../../packages/_module.mjs";import{PASSWORD_SAFE_STRING,USER_ROLES}from"../../../common/constants.mjs";import View from"./view.mjs";export default class PlayersView extends View{route="/players";socket="getPlayersData";_template="players";_methods=["get"];async handleGet(e,s){const{db:t,game:r}=global;if(!r.world)return s.redirect(`${e.baseUrl}/setup`),!1;const a=await t.User.getUsers();if(!PlayersView.#e(a,e.user))return s.redirect(`${e.baseUrl}/join`),!1;const o=r.world.background,i=o?`body.background {\n --background-url: url("${URL.parseSafe(o)?o:foundry.utils.getRoute(o,{prefix:express.routePrefix})}");\n }`:"",l=View._getStaticContent({setup:!0});s.render(this._template,{layout:"setup",bodyClass:["auth","players","flexcol",o?"background":"",`theme-${config.options.cssTheme}`].filterJoin(" "),pageTitle:r.world.title,messages:sessions.getMessages(e,{clear:!1}),scripts:l.scripts,styles:l.styles,inlineStyles:i})}async handleSocket(e,s){if(!game.world||!game.ready)return s({});const t=e.worlds[game.world.id]||null;return e.admin||PlayersView.#e(game.users,t)?s({modules:Module.getPackages({coreTranslation:!0}).map((e=>e.vend())),options:{language:global.config.options.language},passwordString:PASSWORD_SAFE_STRING,release:global.release,settings:await db.Setting.dump(),userId:t,users:await db.User.dump()}):s({})}static#e(e,s){return!e.some((e=>e.hasRole(USER_ROLES.GAMEMASTER)))||!!s&&(e.find((e=>e._id===s))?.isGM??!1)}}
|
||||
1
resources/app/dist/server/views/quit.mjs
vendored
Normal file
1
resources/app/dist/server/views/quit.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import View from"./view.mjs";import sessions from"../../sessions.mjs";export default class QuitView extends View{route="/quit";_methods=["post"];async handlePost(s,e){const{config:t}=global;if(!sessions.authenticateAdmin(s,e).success)return e.send({status:"failed"});e.send({status:"failed"}),t.app?t.app.quit():process.exit()}}
|
||||
1
resources/app/dist/server/views/setup.mjs
vendored
Normal file
1
resources/app/dist/server/views/setup.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/app/dist/server/views/stream.mjs
vendored
Normal file
1
resources/app/dist/server/views/stream.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import GameView from"./game.mjs";export default class StreamView extends GameView{route="/stream";_template="stream"}
|
||||
1
resources/app/dist/server/views/update.mjs
vendored
Normal file
1
resources/app/dist/server/views/update.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import View from"./view.mjs";import sessions from"../../sessions.mjs";import{Module}from"../../packages/_module.mjs";import*as packages from"../../packages/views.mjs";export default class ApplicationUpdateView extends View{route="/update";socket="getUpdateData";_template="update";_methods=["get","post"];async handleGet(e,s){if(config.license.needsSignature)return s.redirect(`${e.baseUrl}/license`);if(global.game.world)return e.user?s.redirect(`${e.baseUrl}/game`):s.redirect(`${e.baseUrl}/join`);if(!sessions.authenticateAdmin(e,s).success)return s.redirect(`${e.baseUrl}/auth`);const a=View._getStaticContent({setup:!0}),t={layout:"setup",bodyClass:`update auth flexcol theme-${config.options.cssTheme}`,messages:sessions.getMessages(e,{clear:!1}),requireAuth:!1,scripts:a.scripts,styles:a.styles};s.render(this._template,t)}async handlePost(e,s){let a={};const{config:t,game:r}=global,{action:o,...i}=e.body,c=sessions.authenticateAdmin(e,s);if(!(!r.world&&c.success))return s.status(403),s.json({error:"You lack server administrator permission to submit this request."});switch(o){case"updateCheck":try{a=await t.updater.check(i),a||(a={info:"SETUP.UpdateNotAvailable"})}catch(e){const{message:s,stack:t,messageCode:r}=e;a={error:s,stack:t,messageCode:r}}break;case"updateDownload":try{a=await t.updater.update()}catch(e){a={error:e.message,stack:e.stack}}break;case"createSnapshot":a=packages.handleCreateSnapshot(e.body);break;case"checkCreateSnapshotDiskSpace":a=await global.packages.backups.checkCreateSnapshotDiskSpace();break;case"previewCompatibility":a=await packages.handlePreviewCompatibility(e.body);break;default:a={error:`Unsupported ApplicationUpdateView action "${o}" submitted`}}return s.json(a)}async handleSocket(e,s){const a=global.config;if(a.adminPassword&&!e.admin||game.world)return s({});return s({options:a.options.vend(),release:global.release,addresses:a.express.getInvitationLinks(),coreUpdate:await a.updater.checkCoreUpdateAvailability(),worlds:[],systems:[],modules:Module.getPackages({coreTranslation:!0}).map((e=>e.vend()))})}}
|
||||
1
resources/app/dist/server/views/upload.mjs
vendored
Normal file
1
resources/app/dist/server/views/upload.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import View from"./view.mjs";import Files from"../../files/files.mjs";import{hasFileExtension}from"../../../common/data/validators.mjs";import{handleDocumentAssetUpload}from"../../database/sanitization.mjs";export default class FileUploadView extends View{route="/upload";_methods=["post"];async handlePost(e,s){const{game:o,config:a}=global;let t=null;if(!(e.session.admin||!o.ready&&!a.adminPassword)&&(o.ready&&(t=await db.User.get(e.user)),!t||!t.hasPermission("FILES_UPLOAD")))throw new Error(`User ${n.userId} does not have permission to upload files.`);let i=e.body.source;const r=e.files.upload,n={...e.body};if(n.uuid){if(!hasFileExtension(r.name,Object.keys(CONST.IMAGE_FILE_EXTENSIONS)))return s.json({error:"Not an image file."});i="data",n.target=await handleDocumentAssetUpload(n.uuid,r)}const d=await Files.upload(i,r,n).catch((e=>({error:e.message||e})));return s.json(d)}}
|
||||
1
resources/app/dist/server/views/view.mjs
vendored
Normal file
1
resources/app/dist/server/views/view.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import Express from"../express.mjs";import{Module}from"../../packages/_module.mjs";export default class View{route;socket=!1;_template;_methods=[];get hasGet(){return this._methods.includes("get")}get hasPost(){return this._methods.includes("post")}async handleGet(e,s){}async handlePost(e,s){}async handleSocket(e,s){}_noWorld(e,s){return View.error(e,s,{pageTitle:"No Active Game",message:"There is currently no active game session. Please wait for the host to configure the world and then refresh this page."})}static error(e,s,...t){const r=t.pop()||global.fatalError,o=this._getStaticContent({setup:!0});s.render("error",{layout:"setup",bodyClass:["auth","error","flexcol",`theme-${config.options.cssTheme}`].join(" "),pageTitle:r.title||"Critical Failure!",message:r.message||"Something went wrong with the Foundry Virtual Tabletop server.",setupUrl:foundry.utils.getRoute("setup",{prefix:express.routePrefix}),stack:r.stack||null,styles:o.styles})}static home(e,s){const{config:t,game:r}=global;return t.license.needsSignature?s.redirect(`${e.baseUrl}/license`):r.world?e.user?s.redirect(`${e.baseUrl}/game`):s.redirect(`${e.baseUrl}/join`):s.redirect(`${e.baseUrl}/setup`)}static _getStaticContent({world:e=!1,setup:s=!1,moduleConfig:t={}}){const r=[];let o=[];const i=new Set,c=(e,s,t)=>{const c="style"===s?o:r;i.has(e)||c.push({src:e,type:s,priority:t,isModule:"module"===s})},l=0,a=1,n=2,p=3,u=4,d=5,h=6,m=7,f=8,y=9,g=10,E=0,_=1,S=2,w=3,b=4;if(Express.CORE_VIEW_SCRIPTS.forEach((e=>c(e,"script",l))),e&&c("scripts/simplepeer.min.js","script",l),Express.CORE_VIEW_MODULES.forEach((e=>c(e,"module",a))),Express.CORE_VIEW_STYLES.forEach((e=>c(e,"style",_))),e){const e=global.game.world;e.system.esmodules.forEach((e=>c(e,"module",h))),e.system.scripts.forEach((e=>c(e,"script",d))),e.system.styles.forEach((e=>c(e,"style",S)));for(let s of e.modules){if(!0!==t[s.id])continue;const e=s.library??!1;s.esmodules.forEach((s=>c(s,"module",e?u:f))),s.scripts.forEach((s=>c(s,"script",e?p:m))),s.styles.forEach((s=>c(s,"style",e?E:w)))}e.esmodules.forEach((e=>c(e,"module",g))),e.scripts.forEach((e=>c(e,"script",y))),e.styles.forEach((e=>c(e,"style",b)))}if(c("scripts/foundry-esm.js","script",n),c("scripts/foundry.js","script",n),s){o.find((e=>"css/style.css"===e.src)).src="css/foundry2.css",c("scripts/setup.js","script",n);Module.getCoreTranslationStyles().forEach((e=>c(e,"style",w)))}const x=(e,s)=>e.priority-s.priority;return r.sort(x),o.sort(x),{scripts:r,styles:o}}}
|
||||
1
resources/app/dist/sessions.mjs
vendored
Normal file
1
resources/app/dist/sessions.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import cookie from"cookie";import{randomString,testPassword,getSalt}from"./core/auth.mjs";import{USER_ROLES}from"../common/constants.mjs";class ClientSessions{constructor(){this.sessions=new Map}static COOKIE_MAX_AGE=864e5;assign(s,e){s.cookie("session",e.id,{maxAge:this.constructor.COOKIE_MAX_AGE,sameSite:"strict"})}expire(s,e){const t=this.get(s,e);global.logger.info(`Expiring client session ${t.id}`),this.sessions.delete(t.id),e.clearCookie("session")}get(s,e){const t=s.headers.cookie?cookie.parse(s.headers.cookie):null;if(!t||!t.session)return null;const o=this.sessions.get(t.session);if(!o)return null;return Date.now()>o.expires?(this.expire(s,e),null):o}getOrCreate(s,e){if(s.session)return s.session;let t=this.get(s,e);return t||(t=this._create(),this.assign(e,t),global.logger.info(`Created client session ${t.id}`)),s.session=t}_create({world:s=null,user:e=null,admin:t=!1}={}){const o=randomString(24),i=new Date,r=i.setDate(i.getDate()+1),n={};s&&e&&(n[s]=e);const a={id:o,admin:t,expires:r,worlds:n,messages:[]};this.sessions.set(o,a);for(let s of global.startupMessages)["error","warn"].includes(s.level)&&a.messages.push({type:s.level,message:s.message,options:{permanent:!0}});return a}async authenticateUser(s,e){const{game:t}=global;if(!t.ready||!t.world)throw new Error("You cannot log until a game World is ready for play.");const o=this.getOrCreate(s,e),{userid:i,password:r}=s.body,n=await db.User.get(i);if(!n)return e.status(401),e.send("JOIN.ErrorUserDoesNotExist"),{request:"join",status:"failed"};let a;try{a=testPassword(r,n.password,n.passwordSalt)}catch(s){a=!1}return a?n.role===USER_ROLES.NONE?(e.status(401),e.send("JOIN.ErrorBanned"),{request:"join",status:"failed"}):(o.worlds[t.world.id]=n.id,logger.info(`User authentication successful for user ${n.name}`,{session:o.id,ip:s.ip}),t.world.onUserLogin(n),{request:"join",status:"success",message:"JOIN.LoginSuccess",redirect:s.baseUrl+"/game"}):(logger.warn(`User authentication failed for user ${n.name}; invalid password`,{status:401,session:o.id,ip:s.ip}),e.status(401),e.send("JOIN.ErrorInvalidPassword"),{request:"join",status:"failed"})}authenticateAdmin(s,e){const{logger:t}=config,o=this.getOrCreate(s,e),i=config.adminPassword;if(o.admin||!i)return{success:!0,session:o};let r=!1;return s.body?.hasOwnProperty("adminPassword")&&(r=testPassword(s.body.adminPassword,i,getSalt(config.passwordSalt)),r?(t.info(`Administrator authentication successful for session ${o.id}`),o.messages.push({type:"info",message:"Admin authentication successful"})):(e.status(403),t.warn(`Administrator authentication failed for session ${o.id}; invalid password`,{status:403,ip:s.ip}),o.messages.push({type:"error",message:"ERROR.InvalidAdminKey",options:{permanent:!0}}))),o.admin=r,{success:r,session:o}}getMessages(s,{clear:e=!0}={}){if(!s.session)return null;const t=s.session.messages;return e&&(s.session.messages=[]),t.length?JSON.stringify(t):null}setMessage(s,e,t,o){const i=this.get(s,e);i&&i.messages.push({type:t,message:o})}logoutAdmin(s,e){const t=this.get(s,e);t&&(t.admin=!1,global.logger.info(`Revoked admin access for session ${t.id}`))}async logoutWorld(s,e){const{game:t,logger:o}=globalThis;if(!t.active)return;const i=this.get(s,e);if(!i)return;const r=i.worlds[t.world.id];if(delete i.worlds[t.world.id],r){const s=await db.User.get(r);if(!s)return;t.activity.deactivateUser(s)&&(o.info(`Logged user ${r} out of active World`),t.world.onUserLogout(s))}}deactivateUserSession(s){for(let e of this.getUserSessions(s))global.logger.info(`Deactivating session ${e.id} for User ${s}`),this.sessions.delete(e.id)}getUserSessions(s){const e=[];for(let t of this.sessions.values())t.worlds[game.world.id]===s&&e.push(t);return e}}export default new ClientSessions;
|
||||
Reference in New Issue
Block a user