@workadventure/map-starter-kit-core 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,5 @@
1
+ import Mustache from 'https://cdn.jsdelivr.net/npm/mustache@4.2.0/+esm';
2
+
1
3
  // Function to retrieve the list of maps from the API
2
4
  async function getMapsList() {
3
5
  try {
@@ -74,7 +76,90 @@ async function createBackgroundImageFade(images = null) {
74
76
  }, 5000); // Change image every 5 seconds
75
77
  }
76
78
 
77
- // Exporter la fonction pour qu'elle soit accessible depuis index.html
78
- // Use a global declaration for TypeScript/JavaScript
79
- window.getMapsList = getMapsList;
80
- window.createBackgroundImageFade = createBackgroundImageFade;
79
+ // Mustache template for a map card (rendered in loadTMJ)
80
+ const CARD_TEMPLATE = `
81
+ <div class="map-cover" style="background-image: url('{{mapImageUrl}}');"></div>
82
+ <div class="map-date">
83
+ Last edit: {{lastModifiedFormatted}}
84
+ </div>
85
+ <div class="map-name">
86
+ {{mapName}}
87
+ </div>
88
+ <div class="map-detail">
89
+ <div class="map-file">
90
+ <strong>{{filename}}</strong>.tmj
91
+ </div>
92
+ <div class="map-weight">
93
+ <strong>{{size}}</strong>
94
+ <span style="opacity: .5">Mo</span>
95
+ </div>
96
+ </div>
97
+ <div class="map-desc">
98
+ {{mapDescription}}
99
+ </div>
100
+ <div class="map-testurl">
101
+ <a href="#" class="btn" data-map-path="{{path}}">Test my map</a>
102
+ </div>
103
+ `;
104
+
105
+ // Load maps from API and render map cards with Mustache
106
+ async function loadTMJ() {
107
+ try {
108
+ const maps = await getMapsList();
109
+
110
+ const mapImages = maps
111
+ .map((map) => {
112
+ if (map.mapImage) {
113
+ return map.mapImage.startsWith('http') ? map.mapImage : `/${map.mapImage}`;
114
+ }
115
+ return null;
116
+ })
117
+ .filter((img) => img !== null);
118
+
119
+ if (mapImages.length > 0) {
120
+ await createBackgroundImageFade(mapImages);
121
+ }
122
+
123
+ const defaultPlaceholder = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1620" height="1024"><rect fill="%231b2a41" width="100%" height="100%"/></svg>';
124
+ const mapsData = maps.map((map) => {
125
+ const mapImageUrl = map.mapImage
126
+ ? (map.mapImage.startsWith('http') ? map.mapImage : `/${map.mapImage}`)
127
+ : (mapImages.length > 0 ? mapImages[0] : defaultPlaceholder);
128
+ return {
129
+ ...map,
130
+ mapImageUrl,
131
+ mapDescription: map.mapDescription || 'No description available',
132
+ };
133
+ });
134
+
135
+ const mainElement = document.querySelector('main');
136
+ if (!mainElement) return;
137
+
138
+ mainElement.innerHTML = '';
139
+ mapsData.forEach((map) => {
140
+ const section = document.createElement('section');
141
+ section.className = 'card-map';
142
+ section.innerHTML = Mustache.render(CARD_TEMPLATE, map);
143
+
144
+ const testBtn = section.querySelector('.map-testurl a');
145
+ if (testBtn) {
146
+ testBtn.addEventListener('click', (e) => {
147
+ e.preventDefault();
148
+ const host = window.location.host;
149
+ let path = window.location.pathname;
150
+ if (path.endsWith('index.html')) {
151
+ path = path.slice(0, -'index.html'.length);
152
+ }
153
+ const instanceId = Math.random().toString(36).substring(2, 15);
154
+ const url = `https://play.workadventu.re/_/${instanceId}/${host}${path}${map.path}`;
155
+ window.open(url, '_blank');
156
+ });
157
+ }
158
+ mainElement.appendChild(section);
159
+ });
160
+ } catch (error) {
161
+ console.error('Error loading maps:', error);
162
+ }
163
+ }
164
+
165
+ export { getMapsList, getImagesList, createBackgroundImageFade, loadTMJ };
@@ -13,108 +13,11 @@
13
13
  <title>WorkAdventure build your map</title>
14
14
  <link rel="icon" href="public/images/favicon.svg" type="image/svg+xml">
15
15
  <script type="module">
16
- document.addEventListener("DOMContentLoaded", (event) => {
17
- // Load index.js to have access to getMapsList
18
- import('/public/assets/js/index.js').then(() => {
19
- loadTMJ();
16
+ document.addEventListener("DOMContentLoaded", () => {
17
+ import('/public/assets/js/index.js').then((module) => {
18
+ module.loadTMJ();
20
19
  });
21
20
  });
22
-
23
- async function loadTMJ() {
24
- try {
25
- // Get the list of maps from the API
26
- const maps = await window.getMapsList();
27
-
28
- // Retrieve map images for background fade
29
- const mapImages = maps
30
- .map(map => {
31
- if (map.mapImage) {
32
- return map.mapImage.startsWith('http') ? map.mapImage : `/${map.mapImage}`;
33
- }
34
- return null;
35
- })
36
- .filter(img => img !== null);
37
-
38
- // Create background image fade
39
- if (mapImages.length > 0) {
40
- await window.createBackgroundImageFade(mapImages);
41
- }
42
-
43
- // Mustache template for a map card
44
- const cardTemplate = `
45
- <div class="map-cover" style="background-image: url('{{mapImageUrl}}');"></div>
46
- <div class="map-date">
47
- Last edit: {{lastModifiedFormatted}}
48
- </div>
49
- <div class="map-name">
50
- {{mapName}}
51
- </div>
52
- <div class="map-detail">
53
- <div class="map-file">
54
- <strong>{{filename}}</strong>.tmj
55
- </div>
56
- <div class="map-weight">
57
- <strong>{{size}}</strong>
58
- <span style="opacity: .5">Mo</span>
59
- </div>
60
- </div>
61
- <div class="map-desc">
62
- {{mapDescription}}
63
- </div>
64
- <div class="map-testurl">
65
- <a href="#" class="btn" data-map-path="{{path}}">Test my map</a>
66
- </div>
67
- `;
68
-
69
- // Prepare data for Mustache
70
- const mapsData = maps.map(map => {
71
- // Build the image URL - use the first available image or a default image
72
- const mapImageUrl = map.mapImage
73
- ? (map.mapImage.startsWith('http') ? map.mapImage : `/${map.mapImage}`)
74
- : (mapImages.length > 0 ? mapImages[0] : 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1620" height="1024"><rect fill="%231b2a41" width="100%" height="100%"/></svg>');
75
-
76
- return {
77
- ...map,
78
- mapImageUrl: mapImageUrl,
79
- mapDescription: map.mapDescription || 'No description available'
80
- };
81
- });
82
-
83
- // Render each map with Mustache and inject them into the container
84
- const mainElement = document.querySelector('main');
85
- if (mainElement) {
86
- // Clear existing content
87
- mainElement.innerHTML = '';
88
-
89
- // Create a section for each map
90
- mapsData.forEach(map => {
91
- const section = document.createElement('section');
92
- section.className = 'card-map';
93
- section.innerHTML = Mustache.render(cardTemplate, map);
94
-
95
- // Add an event handler for the "Test my map" button
96
- const testBtn = section.querySelector('.map-testurl a');
97
- if (testBtn) {
98
- testBtn.addEventListener('click', (e) => {
99
- e.preventDefault();
100
- const host = window.location.host;
101
- let path = window.location.pathname;
102
- if (path.endsWith('index.html')) {
103
- path = path.substr(0, path.length - 'index.html'.length);
104
- }
105
- const instanceId = Math.random().toString(36).substring(2, 15);
106
- const url = `https://play.workadventu.re/_/${instanceId}/${host}${path}${map.path}`;
107
- window.open(url, '_blank');
108
- });
109
- }
110
-
111
- mainElement.appendChild(section);
112
- });
113
- }
114
- } catch (error) {
115
- console.error('Error loading maps:', error);
116
- }
117
- }
118
21
  </script>
119
22
  </head>
120
23
 
@@ -150,7 +53,7 @@
150
53
  </div>
151
54
  </header>
152
55
  <main>
153
- <!-- Map cards will be injected here dynamically by Mustache -->
56
+ <!-- Map cards are injected here by index.js -->
154
57
  </main>
155
58
  <div class="button-wrapper">
156
59
  <div style="flex-grow: 1;">
@@ -15,8 +15,8 @@
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", (event) => {
17
17
  // Load index.js to have access to getMapsList
18
- import('/public/assets/js/index.js').then(() => {
19
- window.createBackgroundImageFade();
18
+ import('/public/assets/js/index.js').then((module) => {
19
+ module.createBackgroundImageFade();
20
20
  });
21
21
  });
22
22
  </script>
@@ -47,8 +47,8 @@
47
47
  <script type="module">
48
48
  document.addEventListener("DOMContentLoaded", (event) => {
49
49
  // Load index.js to have access to getMapsList
50
- import('/public/assets/js/index.js').then(() => {
51
- window.createBackgroundImageFade();
50
+ import('/public/assets/js/index.js').then((module) => {
51
+ module.createBackgroundImageFade();
52
52
  });
53
53
  });
54
54
  </script>
@@ -14,8 +14,8 @@
14
14
  <link rel="icon" href="public/images/favicon.svg" type="image/svg+xml">
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", (event) => {
17
- import('/public/assets/js/index.js').then(() => {
18
- window.createBackgroundImageFade();
17
+ import('/public/assets/js/index.js').then((module) => {
18
+ module.createBackgroundImageFade();
19
19
  });
20
20
  });
21
21
  </script>
@@ -15,8 +15,8 @@
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", (event) => {
17
17
  // Load index.js to have access to getMapsList
18
- import('/public/assets/js/index.js').then(() => {
19
- window.createBackgroundImageFade();
18
+ import('/public/assets/js/index.js').then((module) => {
19
+ module.createBackgroundImageFade();
20
20
  });
21
21
  });
22
22
  </script>
@@ -14,7 +14,7 @@
14
14
  <link rel="icon" href="public/images/favicon.svg" type="image/svg+xml">
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", async () => {
17
- await import('/public/assets/js/index.js');
17
+ const module = await import('/public/assets/js/index.js');
18
18
  const main = document.querySelector('main .maps-container');
19
19
  const emptyEl = document.getElementById('maps-empty');
20
20
  const errorEl = document.getElementById('maps-error');
@@ -84,8 +84,8 @@
84
84
 
85
85
  // Background fade from first map image if available
86
86
  const firstImg = maps[0]?.mapImage;
87
- if (firstImg && typeof window.createBackgroundImageFade === 'function') {
88
- window.createBackgroundImageFade([firstImg]);
87
+ if (firstImg && typeof module.createBackgroundImageFade === 'function') {
88
+ module.createBackgroundImageFade([firstImg]);
89
89
  }
90
90
  } catch (e) {
91
91
  loadingEl.style.display = 'none';
@@ -15,8 +15,8 @@
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", (event) => {
17
17
  // Load index.js to have access to getMapsList
18
- import('/public/assets/js/index.js').then(() => {
19
- window.createBackgroundImageFade();
18
+ import('/public/assets/js/index.js').then((module) => {
19
+ module.createBackgroundImageFade();
20
20
  });
21
21
  });
22
22
  </script>
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sources":["../src/getCoreRoot.ts","../src/controllers/FrontController.ts","../src/controllers/MapController.ts","../src/controllers/UploaderController.ts","../src/server.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet coreRoot: string | null = null;\n\n/**\n * Returns the root directory of the \"core\" package (app + templates + public).\n * - When running from the project root: returns process.cwd().\n * - When running from packages/map-starter-kit-core or node_modules: returns the package directory.\n * Allows updating the core package without touching user files (maps, .env, tilesets).\n */\nexport function getCoreRoot(): string {\n if (coreRoot !== null) {\n return coreRoot;\n }\n try {\n const dir = path.dirname(fileURLToPath(import.meta.url));\n const candidate = path.dirname(dir);\n const packagePath = path.join(candidate, \"package.json\");\n if (fs.existsSync(packagePath)) {\n const pkg = JSON.parse(fs.readFileSync(packagePath, \"utf-8\"));\n if (pkg.name === \"@workadventure/map-starter-kit-core\") {\n coreRoot = candidate;\n return coreRoot;\n }\n }\n } catch {\n // ignore\n }\n coreRoot = process.cwd();\n return coreRoot;\n}\n\n/**\n * Override the core root (e.g. for tests or custom layout).\n */\nexport function setCoreRoot(root: string): void {\n coreRoot = root;\n}\n","import express from 'express';\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport Mustache from 'mustache';\nimport { getCoreRoot } from '../getCoreRoot.js';\n\nexport class FrontController {\n private app: express.Application;\n\n constructor(app: express.Application) {\n this.app = app;\n // Assets are now configured in app.ts\n this.setupRoutes();\n this.setupRoutesStep1();\n this.setupRoutesStep2();\n this.setupRoutesStep3();\n this.setupRoutesStep4();\n }\n\n private setupRoutes() {\n // Route for the Mustache renderer on \"/\"\n this.app.get('/', async (_, res) => {\n try {\n res.send(await this.renderTemplate('index'));\n } catch (error) {\n console.error('Error rendering template:', error);\n res.status(500).send('Error rendering template');\n }\n });\n }\n\n /**\n * Render a template file\n * @param filename - The filename of the template to render\n * @returns The rendered template\n */\n private async renderTemplate(filename: string): Promise<string> {\n const coreRoot = getCoreRoot();\n const templatesDir = path.join(coreRoot, 'public/assets/views');\n if(!fs.existsSync(templatesDir)) {\n throw new Error(`Templates directory not found: ${templatesDir}`);\n }\n const templatePath = path.join(templatesDir, `${filename}.html`);\n const template = await fs.promises.readFile(templatePath, 'utf-8');\n // Render the template with Mustache (without data for now)\n return Mustache.render(template, {});\n }\n\n /**\n * Setup the routes for file \"step1-git.html\"\n * @returns void\n */\n private setupRoutesStep1() {\n this.app.get('/step1-git', async (_, res) => {\n res.send(await this.renderTemplate('step1-git'));\n });\n }\n\n /**\n * Setup the routes for file \"step2-hosting.html\"\n * @returns void\n */\n private setupRoutesStep2() {\n this.app.get('/step2-hosting', async (_, res) => {\n res.send(await this.renderTemplate('step2-hosting'));\n });\n }\n\n /**\n * Setup the routes for file \"step3-steps.html\"\n * @returns void\n */\n private setupRoutesStep3() {\n this.app.get('/step3-steps', async (_, res) => {\n res.send(await this.renderTemplate('step3-steps'));\n });\n this.app.get('/step3-steps-selfhosted', async (_, res) => {\n res.send(await this.renderTemplate('step3-steps-selfhosted'));\n });\n }\n\n /**\n * Setup the routes for file \"step4-map.html\"\n * @returns void\n */\n private setupRoutesStep4() {\n this.app.get('/step4-validated', async (_, res) => {\n res.send(await this.renderTemplate('step4-validated'));\n });\n this.app.get('/step4-validated-selfhosted', async (_, res) => {\n res.send(await this.renderTemplate('step4-validated-selfhosted'));\n });\n }\n \n}\n","import express from 'express';\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport class MapController {\n private router: express.Router;\n private app: express.Application;\n\n constructor(app: express.Application) {\n this.app = app;\n this.router = express.Router();\n this.setupRoutes();\n // Register the router on the application with the \"/maps\" prefix\n this.app.use('/maps', this.router);\n }\n\n /**\n * Setup the routes for the map controller\n * @returns void\n */\n private setupRoutes() {\n // Route to retrieve the list of maps with their properties\n this.router.get('/list', async (_, res) => {\n try {\n const mapsDir = './';\n const maps = await this.getMapsWithProperties(mapsDir);\n res.json(maps);\n } catch (error) {\n console.error('Error getting maps list:', error);\n res.status(500).json({ error: 'Error getting maps list' });\n }\n });\n }\n\n /**\n * Get the list of maps with their properties\n * @param dir - The directory to search for maps\n * @param baseDir - The base directory to use for relative paths\n * @returns The list of maps with their properties\n */\n private async getMapsWithProperties(dir: string, baseDir: string = dir): Promise<any[]> {\n let files = await fs.promises.readdir(dir, { withFileTypes: true });\n const maps: any[] = [];\n\n for (const file of files) {\n const fullPath = path.join(dir, file.name);\n\n // Exclude the \"dist\" folder\n if(file.name === 'dist') continue;\n\n if (file.isDirectory()) {\n // Recursively search subdirectories\n const subMaps = await this.getMapsWithProperties(fullPath, baseDir);\n maps.push(...subMaps);\n } else if (file.name.endsWith('.tmj')) {\n try {\n // Read and parse TMJ file\n const tmjContent = await fs.promises.readFile(fullPath, 'utf-8');\n const tmjData = JSON.parse(tmjContent);\n\n // Extract properties\n const properties = tmjData.properties || [];\n const findProperty = (key: string) => {\n const item = properties.find((p: any) => p.name === key);\n return item ? item.value : null;\n };\n\n // Get file stats for size and date\n const stats = await fs.promises.stat(fullPath);\n const fileSizeInMB = (stats.size / (1024 * 1024)).toFixed(2);\n const lastModified = stats.mtime;\n\n // Get relative path\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, '/');\n\n // Extract filename without extension\n const filename = path.basename(file.name, '.tmj');\n\n maps.push({\n path: relativePath,\n filename: filename,\n mapName: findProperty('mapName') || filename,\n mapImage: findProperty('mapImage') || null,\n mapDescription: findProperty('mapDescription') || '',\n mapCopyright: findProperty('mapCopyright') || '',\n size: fileSizeInMB,\n lastModified: lastModified.toISOString(),\n lastModifiedFormatted: lastModified.toLocaleDateString('fr-FR', {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n })\n });\n } catch (error) {\n console.error(`Error reading TMJ file ${fullPath}:`, error);\n }\n }\n }\n\n return maps;\n }\n}\n","import express from 'express';\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\n/** Metadata returned by map-storage for each map (WAM file). */\nexport interface MapStorageMetadata {\n copyright?: string;\n description?: string;\n name?: string;\n thumbnail?: string;\n}\n\n/** Single map entry returned by map-storage GET /maps/ (value in the map). */\nexport interface MapStorageEntry {\n mapUrl: string;\n wamFileUrl?: string;\n metadata?: MapStorageMetadata;\n}\n\n/** Response shape: Map<wamfile, { mapUrl, metadata }>. In JSON this is a plain object. */\nexport interface MapStorageListResponse {\n [wamFile: string]: MapStorageEntry;\n}\n\n/** Hydrated map item returned by our API to the frontend. */\nexport interface MapListItem {\n wamFileUrl: string;\n filename: string;\n mapName: string | null;\n mapDescription?: string;\n mapCopyright?: string;\n mapImage: string | null;\n mapUrl: string;\n}\n\n/**\n * Converts the map-storage response (Map<wamfile, entry>) into a list of MapListItem.\n */\nexport function hydrateMapStorageList(\n raw: MapStorageListResponse,\n mapStorageBaseUrl: string\n): MapListItem[] {\n const list: MapListItem[] = [];\n const baseUrl = mapStorageBaseUrl.replace(/\\/$/, '');\n\n for (const [wamFile, entry] of Object.entries(raw)) {\n if (!entry || typeof entry !== 'object') continue;\n\n const rawWamFileUrl = entry.wamFileUrl ?? wamFile;\n const wamFileUrlNormalized = rawWamFileUrl.startsWith('http')\n ? new URL(rawWamFileUrl).pathname.replace(/^\\//, '')\n : rawWamFileUrl.replace(/^\\//, '');\n\n const filename = wamFileUrlNormalized.split('/').pop() ?? wamFileUrlNormalized;\n const nameFromFile = filename.replace(/\\.(tmj|wam)$/i, '');\n\n const meta = entry.metadata ?? {};\n const mapUrl = entry.mapUrl ?? (entry as unknown as { mapUrl?: string }).mapUrl ?? '';\n\n let mapImage: string | null = null;\n if (meta.thumbnail) {\n if (meta.thumbnail.startsWith('http')) {\n mapImage = meta.thumbnail;\n } else {\n const thumbFile = meta.thumbnail.replace(/^\\//, '');\n const wamDir = wamFileUrlNormalized.includes('/')\n ? wamFileUrlNormalized.replace(/\\/[^/]*$/, '')\n : '';\n const thumbPath = wamDir ? `${wamDir}/${thumbFile}` : thumbFile;\n mapImage = `${baseUrl}/${thumbPath}`;\n }\n }\n\n list.push({\n wamFileUrl: wamFileUrlNormalized,\n filename,\n mapName: meta.name ?? nameFromFile,\n mapDescription: meta.description,\n mapCopyright: meta.copyright,\n mapImage,\n mapUrl: mapUrl.startsWith('http') ? mapUrl : `${baseUrl}/${mapUrl.replace(/^\\//, '')}`,\n });\n }\n\n return list;\n}\n\nexport class UploaderController {\n private router: express.Router;\n private app: express.Application;\n\n constructor(app: express.Application) {\n this.app = app;\n this.router = express.Router();\n this.setupMiddleware();\n this.setupRoutes();\n // Register the router on the application with the \"/uploader\" prefix\n this.app.use('/uploader', this.router);\n }\n\n private setupMiddleware() {\n // Middleware to parse JSON bodies\n this.router.use(express.json());\n }\n\n private setupRoutes() {\n // Route to configure MAP_STORAGE mode\n this.router.post('/configure', async (req, res) => {\n try {\n const { mapStorageUrl, mapStorageApiKey, uploadDirectory } = req.body;\n\n // Validate required fields\n if (!mapStorageUrl || !mapStorageApiKey || !uploadDirectory) {\n return res.status(400).json({\n error: 'Missing required fields',\n required: ['mapStorageUrl', 'mapStorageApiKey', 'uploadDirectory']\n });\n }\n\n // Create or update .env.secret file\n await this.createEnvSecretFile({\n mapStorageUrl,\n mapStorageApiKey,\n uploadDirectory\n });\n\n return res.json({\n success: true,\n message: 'Configuration updated successfully'\n });\n } catch (error) {\n console.error('Error configuring uploader:', error);\n return res.status(500).json({\n error: 'Error configuring uploader',\n message: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n });\n\n // Route to get current configuration status\n this.router.get('/status', async (_, res) => {\n try {\n const envSecretPath = path.join(process.cwd(), '.env.secret');\n\n const hasSecretFile = await fs.promises.access(envSecretPath)\n .then(() => true)\n .catch(() => false);\n\n let secretConfig: {\n mapStorageUrl: string | null;\n mapStorageApiKey: string | null;\n uploadDirectory: string | null;\n } | null = null;\n if (hasSecretFile) {\n const secretContent = await fs.promises.readFile(envSecretPath, 'utf-8');\n const mapStorageUrl = secretContent.match(/MAP_STORAGE_URL=(.+)/)?.[1]?.trim();\n const mapStorageApiKey = secretContent.match(/MAP_STORAGE_API_KEY=(.+)/)?.[1]?.trim();\n const uploadDirectory = secretContent.match(/UPLOAD_DIRECTORY=(.+)/)?.[1]?.trim();\n\n secretConfig = {\n mapStorageUrl: mapStorageUrl || null,\n mapStorageApiKey: mapStorageApiKey || null, // Hide the actual key\n uploadDirectory: uploadDirectory || null\n };\n }\n return res.json({\n hasSecretFile,\n secretConfig\n });\n } catch (error) {\n console.error('Error getting uploader status:', error);\n return res.status(500).json({\n error: 'Error getting uploader status',\n message: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n });\n\n // Route to get list of maps from map-storage (for self-hosted step4)\n this.router.get('/maps-storage-list', async (_, res) => {\n try {\n const envSecretPath = path.join(process.cwd(), '.env.secret');\n const hasSecretFile = await fs.promises.access(envSecretPath)\n .then(() => true)\n .catch(() => false);\n\n if (!hasSecretFile) {\n return res.status(400).json({\n error: 'Configuration not found',\n message: 'Please configure the upload settings first.'\n });\n }\n\n const secretContent = await fs.promises.readFile(envSecretPath, 'utf-8');\n const mapStorageUrl = secretContent.match(/MAP_STORAGE_URL=(.+)/)?.[1]?.trim();\n const mapStorageApiKey = secretContent.match(/MAP_STORAGE_API_KEY=(.+)/)?.[1]?.trim();\n\n if (!mapStorageUrl || !mapStorageApiKey) {\n return res.status(400).json({\n error: 'Missing map-storage configuration',\n message: 'MAP_STORAGE_URL and MAP_STORAGE_API_KEY must be set in .env.secret.'\n });\n }\n\n const baseUrl = mapStorageUrl.replace(/\\/$/, '');\n const listUrl = `${baseUrl}/maps/`;\n\n const response = await fetch(listUrl, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${mapStorageApiKey}`,\n 'Content-Type': 'application/json'\n }\n });\n\n if (!response.ok) {\n const text = await response.text();\n console.error('Map-storage list error:', response.status, text);\n return res.status(response.status).json({\n error: 'Map-storage request failed',\n message: response.status === 401\n ? 'Invalid API key. Check MAP_STORAGE_API_KEY in your .env.secret.'\n : `Map-storage returned ${response.status}: ${text.slice(0, 200)}`\n });\n }\n\n const data = await response.json();\n // Map-storage returns Map<wamfile, { mapUrl, metadata }> (as a plain object in JSON)\n const raw = typeof data === 'object' && data !== null ? data : {};\n const rawMap: MapStorageListResponse = !Array.isArray(raw) && typeof raw.maps === 'object' && raw.maps !== null\n ? (raw.maps as MapStorageListResponse)\n : !Array.isArray(raw)\n ? (raw as MapStorageListResponse)\n : {};\n const maps = hydrateMapStorageList(rawMap, baseUrl);\n const playBaseUrl = secretContent.match(/PLAY_BASE_URL=(.+)/)?.[1]?.trim() || null;\n return res.json({ maps, mapStorageUrl: baseUrl, playBaseUrl });\n } catch (error) {\n console.error('Error fetching maps from map-storage:', error);\n return res.status(500).json({\n error: 'Error fetching maps list',\n message: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n });\n\n // Route to upload map\n this.router.post('/upload', async (_, res) => {\n try {\n // Verify that configuration exists\n const envSecretPath = path.join(process.cwd(), '.env.secret');\n\n // Check if .env.secret exists\n const hasSecretFile = await fs.promises.access(envSecretPath)\n .then(() => true)\n .catch(() => false);\n\n if (!hasSecretFile) {\n return res.status(400).json({\n error: 'Configuration not found',\n message: 'Please configure the upload settings first using /uploader/configure'\n });\n }\n\n // Execute upload\n await this.runUpload();\n\n return res.json({\n success: true,\n message: 'Map uploaded successfully'\n });\n } catch (error) {\n console.error('Error uploading map:', error);\n return res.status(500).json({\n error: 'Error uploading map',\n message: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n });\n }\n\n private async runUpload(): Promise<void> {\n const projectRoot = process.cwd();\n \n try {\n // Execute npm run upload-only\n // The command will read environment variables from .env and .env.secret\n const { stdout, stderr } = await execAsync('npm run upload', {\n cwd: projectRoot,\n env: {\n ...process.env,\n // Ensure we're using the current process environment\n NODE_ENV: process.env.NODE_ENV || 'development'\n },\n maxBuffer: 10 * 1024 * 1024 // 10MB buffer for output\n });\n\n if (stderr && !stderr.includes('warning')) {\n console.warn('Upload stderr:', stderr);\n }\n \n console.info('Upload stdout:', stdout);\n console.info('Upload completed successfully');\n } catch (error) {\n console.error('Error executing upload-only:', error);\n throw error;\n }\n }\n\n private async createEnvSecretFile(config: {\n mapStorageUrl: string;\n mapStorageApiKey: string;\n uploadDirectory: string;\n }): Promise<void> {\n const envSecretPath = path.join(process.cwd(), '.env.secret');\n\n // Create the .env.secret file with the provided configuration\n const secretContent = `# Secret configuration file for MAP_STORAGE upload mode\n# This file is not committed to git (see .gitignore)\n\nMAP_STORAGE_URL=${config.mapStorageUrl}\nMAP_STORAGE_API_KEY=${config.mapStorageApiKey}\nUPLOAD_DIRECTORY=${config.uploadDirectory}\n`;\n\n await fs.promises.writeFile(envSecretPath, secretContent, 'utf-8');\n }\n}\n","import express from 'express';\nimport * as path from \"node:path\";\nimport * as fs from \"node:fs\";\nimport cors from 'cors';\nimport { getCoreRoot } from './getCoreRoot.js';\nimport { FrontController } from './controllers/FrontController.js';\nimport { MapController } from './controllers/MapController.js';\nimport { UploaderController } from './controllers/UploaderController.js';\n\nconst app = express();\n\nconst corsOptions = {\n credentials: true, // Allow sending cookies\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: [\n 'Content-Type',\n 'Authorization',\n 'X-Requested-With',\n 'Accept',\n 'Origin',\n 'Access-Control-Request-Method',\n 'Access-Control-Request-Headers'\n ],\n exposedHeaders: ['Content-Length', 'Content-Type'],\n maxAge: 86400, // Cache the OPTIONS requests for 24 hours\n};\n\n// Apply the CORS middleware\n// The CORS middleware automatically handles the OPTIONS requests (preflight)\napp.use(cors(corsOptions));\n\n// Parse JSON bodies\napp.use(express.json());\n\n// Configure the static assets for Express\nconst staticOptions = {\n maxAge: '1d', // Cache the files for 1 day\n etag: true, // Enable ETag for cache validation\n lastModified: true, // Enable Last-Modified header\n};\n\n// Serve dist/assets FIRST with explicit MIME type configuration\n// This ensures compiled JavaScript files from getMapsScripts are served correctly\n// This route must be before express.static('.') to take precedence\napp.use('/assets', express.static(path.join(process.cwd(), 'dist', 'assets'), staticOptions));\n// Serve the public folder from core (project root or package root for easy updates)\napp.use('/public', express.static(path.join(getCoreRoot(), 'public'), staticOptions));\n// Serve the tilesets folder with a longer cache (rarely modified)\napp.use('/tilesets', express.static(path.join(process.cwd(), 'tilesets'), {\n maxAge: '7d',\n etag: true,\n lastModified: true,\n}));\n\n// Middleware to exclude /src from express.static - let Vite handle TypeScript transformation\n// VitePluginNode will automatically add Vite middleware that transforms TypeScript files\nconst staticMiddleware = express.static('.', staticOptions);\n\n// Middleware to transform and serve TypeScript files as JavaScript\n// This bundles the file with its dependencies to resolve npm imports\napp.use('/src', async (req, res, next) => {\n // Only handle .ts and .tsx files - transform them to JavaScript\n if (req.path.endsWith('.ts') || req.path.endsWith('.tsx')) {\n try {\n // req.path includes /src/, so we need to join it correctly\n const filePath = path.join(process.cwd(), 'src', req.path.startsWith('/') ? req.path.slice(1) : req.path);\n \n // Check if file exists\n if (!fs.existsSync(filePath)) {\n return res.status(404).send('File not found');\n }\n \n // Use dynamic import to get esbuild (available via Vite)\n const esbuild = await import('esbuild');\n \n // Bundle the TypeScript file with its dependencies\n // This resolves npm imports like @workadventure/scripting-api-extra\n const result = await esbuild.build({\n entryPoints: [filePath],\n bundle: true,\n format: 'esm',\n target: 'esnext',\n write: false,\n platform: 'browser',\n sourcemap: false,\n // Externalize WorkAdventure global API (available in the browser)\n external: ['WA'],\n });\n \n res.setHeader('Content-Type', 'application/javascript; charset=utf-8');\n return res.send(result.outputFiles[0].text);\n } catch (error) {\n console.error('Error transforming TypeScript file:', error);\n return next(error);\n }\n }\n // For non-TypeScript files in /src, pass to next middleware\n next();\n});\n\n// Serve static files, but skip /src (handled above)\napp.use((req, res, next) => {\n // Skip /src requests - they are handled by the transformation middleware above\n if (req.path.startsWith('/src/')) {\n return next(); // Let the transformation middleware handle it or pass to Vite\n }\n // For other files, use express.static\n staticMiddleware(req, res, next);\n});\n\nconst controllers = [\n new MapController(app),\n new FrontController(app),\n new UploaderController(app),\n];\n\n// Verify and log all controllers created\ncontrollers.forEach(controller => {\n console.info(`Controller started: ${controller.constructor.name}`);\n});\n\nexport default app;\n// Export for VitePluginNode compatibility\nexport const viteNodeApp = app;\n"],"names":["path","fileURLToPath","fs","app","coreRoot","promisify","exec"],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sources":["../src/utils/getCoreRoot.ts","../src/controllers/FrontController.ts","../src/controllers/MapController.ts","../src/controllers/UploaderController.ts","../src/server.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet coreRoot: string | null = null;\n\n/**\n * Returns the root directory of the \"core\" package (app + templates + public).\n * - When running from the project root: returns process.cwd().\n * - When running from packages/map-starter-kit-core or node_modules: returns the package directory.\n * Allows updating the core package without touching user files (maps, .env, tilesets).\n */\nexport function getCoreRoot(): string {\n if (coreRoot !== null) {\n return coreRoot;\n }\n try {\n const dir = path.dirname(fileURLToPath(import.meta.url));\n const candidate = path.dirname(dir);\n const packagePath = path.join(candidate, \"package.json\");\n if (fs.existsSync(packagePath)) {\n const pkg = JSON.parse(fs.readFileSync(packagePath, \"utf-8\"));\n if (pkg.name === \"@workadventure/map-starter-kit-core\") {\n coreRoot = candidate;\n return coreRoot;\n }\n }\n } catch {\n // ignore\n }\n coreRoot = process.cwd();\n return coreRoot;\n}\n\n/**\n * Override the core root (e.g. for tests or custom layout).\n */\nexport function setCoreRoot(root: string): void {\n coreRoot = root;\n}\n","import express from 'express';\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport Mustache from 'mustache';\nimport { getCoreRoot } from '../utils/getCoreRoot.ts';\n\nexport class FrontController {\n private app: express.Application;\n\n constructor(app: express.Application) {\n this.app = app;\n // Assets are now configured in app.ts\n this.setupRoutes();\n this.setupRoutesStep1();\n this.setupRoutesStep2();\n this.setupRoutesStep3();\n this.setupRoutesStep4();\n }\n\n private setupRoutes() {\n // Route for the Mustache renderer on \"/\"\n this.app.get('/', async (_, res) => {\n try {\n res.send(await this.renderTemplate('index'));\n } catch (error) {\n console.error('Error rendering template:', error);\n res.status(500).send('Error rendering template');\n }\n });\n }\n\n /**\n * Render a template file\n * @param filename - The filename of the template to render\n * @returns The rendered template\n */\n private async renderTemplate(filename: string): Promise<string> {\n const coreRoot = getCoreRoot();\n const templatesDir = path.join(coreRoot, 'public/assets/views');\n if(!fs.existsSync(templatesDir)) {\n throw new Error(`Templates directory not found: ${templatesDir}`);\n }\n const templatePath = path.join(templatesDir, `${filename}.html`);\n const template = await fs.promises.readFile(templatePath, 'utf-8');\n // Render the template with Mustache (without data for now)\n return Mustache.render(template, {});\n }\n\n /**\n * Setup the routes for file \"step1-git.html\"\n * @returns void\n */\n private setupRoutesStep1() {\n this.app.get('/step1-git', async (_, res) => {\n res.send(await this.renderTemplate('step1-git'));\n });\n }\n\n /**\n * Setup the routes for file \"step2-hosting.html\"\n * @returns void\n */\n private setupRoutesStep2() {\n this.app.get('/step2-hosting', async (_, res) => {\n res.send(await this.renderTemplate('step2-hosting'));\n });\n }\n\n /**\n * Setup the routes for file \"step3-steps.html\"\n * @returns void\n */\n private setupRoutesStep3() {\n this.app.get('/step3-steps', async (_, res) => {\n res.send(await this.renderTemplate('step3-steps'));\n });\n this.app.get('/step3-steps-selfhosted', async (_, res) => {\n res.send(await this.renderTemplate('step3-steps-selfhosted'));\n });\n }\n\n /**\n * Setup the routes for file \"step4-map.html\"\n * @returns void\n */\n private setupRoutesStep4() {\n this.app.get('/step4-validated', async (_, res) => {\n res.send(await this.renderTemplate('step4-validated'));\n });\n this.app.get('/step4-validated-selfhosted', async (_, res) => {\n res.send(await this.renderTemplate('step4-validated-selfhosted'));\n });\n }\n \n}\n","import express from 'express';\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport class MapController {\n private router: express.Router;\n private app: express.Application;\n\n constructor(app: express.Application) {\n this.app = app;\n this.router = express.Router();\n this.setupRoutes();\n // Register the router on the application with the \"/maps\" prefix\n this.app.use('/maps', this.router);\n }\n\n /**\n * Setup the routes for the map controller\n * @returns void\n */\n private setupRoutes() {\n // Route to retrieve the list of maps with their properties\n this.router.get('/list', async (_, res) => {\n try {\n const mapsDir = './';\n const maps = await this.getMapsWithProperties(mapsDir);\n res.json(maps);\n } catch (error) {\n console.error('Error getting maps list:', error);\n res.status(500).json({ error: 'Error getting maps list' });\n }\n });\n }\n\n /**\n * Get the list of maps with their properties\n * @param dir - The directory to search for maps\n * @param baseDir - The base directory to use for relative paths\n * @returns The list of maps with their properties\n */\n private async getMapsWithProperties(dir: string, baseDir: string = dir): Promise<any[]> {\n let files = await fs.promises.readdir(dir, { withFileTypes: true });\n const maps: any[] = [];\n\n for (const file of files) {\n const fullPath = path.join(dir, file.name);\n\n // Exclude the \"dist\" folder\n if(file.name === 'dist') continue;\n\n if (file.isDirectory()) {\n // Recursively search subdirectories\n const subMaps = await this.getMapsWithProperties(fullPath, baseDir);\n maps.push(...subMaps);\n } else if (file.name.endsWith('.tmj')) {\n try {\n // Read and parse TMJ file\n const tmjContent = await fs.promises.readFile(fullPath, 'utf-8');\n const tmjData = JSON.parse(tmjContent);\n\n // Extract properties\n const properties = tmjData.properties || [];\n const findProperty = (key: string) => {\n const item = properties.find((p: any) => p.name === key);\n return item ? item.value : null;\n };\n\n // Get file stats for size and date\n const stats = await fs.promises.stat(fullPath);\n const fileSizeInMB = (stats.size / (1024 * 1024)).toFixed(2);\n const lastModified = stats.mtime;\n\n // Get relative path\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, '/');\n\n // Extract filename without extension\n const filename = path.basename(file.name, '.tmj');\n\n maps.push({\n path: relativePath,\n filename: filename,\n mapName: findProperty('mapName') || filename,\n mapImage: findProperty('mapImage') || null,\n mapDescription: findProperty('mapDescription') || '',\n mapCopyright: findProperty('mapCopyright') || '',\n size: fileSizeInMB,\n lastModified: lastModified.toISOString(),\n lastModifiedFormatted: lastModified.toLocaleDateString('fr-FR', {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n })\n });\n } catch (error) {\n console.error(`Error reading TMJ file ${fullPath}:`, error);\n }\n }\n }\n\n return maps;\n }\n}\n","import express from 'express';\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\n/** Metadata returned by map-storage for each map (WAM file). */\nexport interface MapStorageMetadata {\n copyright?: string;\n description?: string;\n name?: string;\n thumbnail?: string;\n}\n\n/** Single map entry returned by map-storage GET /maps/ (value in the map). */\nexport interface MapStorageEntry {\n mapUrl: string;\n wamFileUrl?: string;\n metadata?: MapStorageMetadata;\n}\n\n/** Response shape: Map<wamfile, { mapUrl, metadata }>. In JSON this is a plain object. */\nexport interface MapStorageListResponse {\n [wamFile: string]: MapStorageEntry;\n}\n\n/** Hydrated map item returned by our API to the frontend. */\nexport interface MapListItem {\n wamFileUrl: string;\n filename: string;\n mapName: string | null;\n mapDescription?: string;\n mapCopyright?: string;\n mapImage: string | null;\n mapUrl: string;\n}\n\n/**\n * Converts the map-storage response (Map<wamfile, entry>) into a list of MapListItem.\n */\nexport function hydrateMapStorageList(\n raw: MapStorageListResponse,\n mapStorageBaseUrl: string\n): MapListItem[] {\n const list: MapListItem[] = [];\n const baseUrl = mapStorageBaseUrl.replace(/\\/$/, '');\n\n for (const [wamFile, entry] of Object.entries(raw)) {\n if (!entry || typeof entry !== 'object') continue;\n\n const rawWamFileUrl = entry.wamFileUrl ?? wamFile;\n const wamFileUrlNormalized = rawWamFileUrl.startsWith('http')\n ? new URL(rawWamFileUrl).pathname.replace(/^\\//, '')\n : rawWamFileUrl.replace(/^\\//, '');\n\n const filename = wamFileUrlNormalized.split('/').pop() ?? wamFileUrlNormalized;\n const nameFromFile = filename.replace(/\\.(tmj|wam)$/i, '');\n\n const meta = entry.metadata ?? {};\n const mapUrl = entry.mapUrl ?? (entry as unknown as { mapUrl?: string }).mapUrl ?? '';\n\n let mapImage: string | null = null;\n if (meta.thumbnail) {\n if (meta.thumbnail.startsWith('http')) {\n mapImage = meta.thumbnail;\n } else {\n const thumbFile = meta.thumbnail.replace(/^\\//, '');\n const wamDir = wamFileUrlNormalized.includes('/')\n ? wamFileUrlNormalized.replace(/\\/[^/]*$/, '')\n : '';\n const thumbPath = wamDir ? `${wamDir}/${thumbFile}` : thumbFile;\n mapImage = `${baseUrl}/${thumbPath}`;\n }\n }\n\n list.push({\n wamFileUrl: wamFileUrlNormalized,\n filename,\n mapName: meta.name ?? nameFromFile,\n mapDescription: meta.description,\n mapCopyright: meta.copyright,\n mapImage,\n mapUrl: mapUrl.startsWith('http') ? mapUrl : `${baseUrl}/${mapUrl.replace(/^\\//, '')}`,\n });\n }\n\n return list;\n}\n\nexport class UploaderController {\n private router: express.Router;\n private app: express.Application;\n\n constructor(app: express.Application) {\n this.app = app;\n this.router = express.Router();\n this.setupMiddleware();\n this.setupRoutes();\n // Register the router on the application with the \"/uploader\" prefix\n this.app.use('/uploader', this.router);\n }\n\n private setupMiddleware() {\n // Middleware to parse JSON bodies\n this.router.use(express.json());\n }\n\n private setupRoutes() {\n // Route to configure MAP_STORAGE mode\n this.router.post('/configure', async (req, res) => {\n try {\n const { mapStorageUrl, mapStorageApiKey, uploadDirectory } = req.body;\n\n // Validate required fields\n if (!mapStorageUrl || !mapStorageApiKey || !uploadDirectory) {\n return res.status(400).json({\n error: 'Missing required fields',\n required: ['mapStorageUrl', 'mapStorageApiKey', 'uploadDirectory']\n });\n }\n\n // Create or update .env.secret file\n await this.createEnvSecretFile({\n mapStorageUrl,\n mapStorageApiKey,\n uploadDirectory\n });\n\n return res.json({\n success: true,\n message: 'Configuration updated successfully'\n });\n } catch (error) {\n console.error('Error configuring uploader:', error);\n return res.status(500).json({\n error: 'Error configuring uploader',\n message: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n });\n\n // Route to get current configuration status\n this.router.get('/status', async (_, res) => {\n try {\n const envSecretPath = path.join(process.cwd(), '.env.secret');\n\n const hasSecretFile = await fs.promises.access(envSecretPath)\n .then(() => true)\n .catch(() => false);\n\n let secretConfig: {\n mapStorageUrl: string | null;\n mapStorageApiKey: string | null;\n uploadDirectory: string | null;\n } | null = null;\n if (hasSecretFile) {\n const secretContent = await fs.promises.readFile(envSecretPath, 'utf-8');\n const mapStorageUrl = secretContent.match(/MAP_STORAGE_URL=(.+)/)?.[1]?.trim();\n const mapStorageApiKey = secretContent.match(/MAP_STORAGE_API_KEY=(.+)/)?.[1]?.trim();\n const uploadDirectory = secretContent.match(/UPLOAD_DIRECTORY=(.+)/)?.[1]?.trim();\n\n secretConfig = {\n mapStorageUrl: mapStorageUrl || null,\n mapStorageApiKey: mapStorageApiKey || null, // Hide the actual key\n uploadDirectory: uploadDirectory || null\n };\n }\n return res.json({\n hasSecretFile,\n secretConfig\n });\n } catch (error) {\n console.error('Error getting uploader status:', error);\n return res.status(500).json({\n error: 'Error getting uploader status',\n message: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n });\n\n // Route to get list of maps from map-storage (for self-hosted step4)\n this.router.get('/maps-storage-list', async (_, res) => {\n try {\n const envSecretPath = path.join(process.cwd(), '.env.secret');\n const hasSecretFile = await fs.promises.access(envSecretPath)\n .then(() => true)\n .catch(() => false);\n\n if (!hasSecretFile) {\n return res.status(400).json({\n error: 'Configuration not found',\n message: 'Please configure the upload settings first.'\n });\n }\n\n const secretContent = await fs.promises.readFile(envSecretPath, 'utf-8');\n const mapStorageUrl = secretContent.match(/MAP_STORAGE_URL=(.+)/)?.[1]?.trim();\n const mapStorageApiKey = secretContent.match(/MAP_STORAGE_API_KEY=(.+)/)?.[1]?.trim();\n\n if (!mapStorageUrl || !mapStorageApiKey) {\n return res.status(400).json({\n error: 'Missing map-storage configuration',\n message: 'MAP_STORAGE_URL and MAP_STORAGE_API_KEY must be set in .env.secret.'\n });\n }\n\n const baseUrl = mapStorageUrl.replace(/\\/$/, '');\n const listUrl = `${baseUrl}/maps/`;\n\n const response = await fetch(listUrl, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${mapStorageApiKey}`,\n 'Content-Type': 'application/json'\n }\n });\n\n if (!response.ok) {\n const text = await response.text();\n console.error('Map-storage list error:', response.status, text);\n return res.status(response.status).json({\n error: 'Map-storage request failed',\n message: response.status === 401\n ? 'Invalid API key. Check MAP_STORAGE_API_KEY in your .env.secret.'\n : `Map-storage returned ${response.status}: ${text.slice(0, 200)}`\n });\n }\n\n const data = await response.json();\n // Map-storage returns Map<wamfile, { mapUrl, metadata }> (as a plain object in JSON)\n const raw = typeof data === 'object' && data !== null ? data : {};\n const rawMap: MapStorageListResponse = !Array.isArray(raw) && typeof raw.maps === 'object' && raw.maps !== null\n ? (raw.maps as MapStorageListResponse)\n : !Array.isArray(raw)\n ? (raw as MapStorageListResponse)\n : {};\n const maps = hydrateMapStorageList(rawMap, baseUrl);\n const playBaseUrl = secretContent.match(/PLAY_BASE_URL=(.+)/)?.[1]?.trim() || null;\n return res.json({ maps, mapStorageUrl: baseUrl, playBaseUrl });\n } catch (error) {\n console.error('Error fetching maps from map-storage:', error);\n return res.status(500).json({\n error: 'Error fetching maps list',\n message: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n });\n\n // Route to upload map\n this.router.post('/upload', async (_, res) => {\n try {\n // Verify that configuration exists\n const envSecretPath = path.join(process.cwd(), '.env.secret');\n\n // Check if .env.secret exists\n const hasSecretFile = await fs.promises.access(envSecretPath)\n .then(() => true)\n .catch(() => false);\n\n if (!hasSecretFile) {\n return res.status(400).json({\n error: 'Configuration not found',\n message: 'Please configure the upload settings first using /uploader/configure'\n });\n }\n\n // Execute upload\n await this.runUpload();\n\n return res.json({\n success: true,\n message: 'Map uploaded successfully'\n });\n } catch (error) {\n console.error('Error uploading map:', error);\n return res.status(500).json({\n error: 'Error uploading map',\n message: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n });\n }\n\n private async runUpload(): Promise<void> {\n const projectRoot = process.cwd();\n \n try {\n // Execute npm run upload-only\n // The command will read environment variables from .env and .env.secret\n const { stdout, stderr } = await execAsync('npm run upload', {\n cwd: projectRoot,\n env: {\n ...process.env,\n // Ensure we're using the current process environment\n NODE_ENV: process.env.NODE_ENV || 'development'\n },\n maxBuffer: 10 * 1024 * 1024 // 10MB buffer for output\n });\n\n if (stderr && !stderr.includes('warning')) {\n console.warn('Upload stderr:', stderr);\n }\n \n console.info('Upload stdout:', stdout);\n console.info('Upload completed successfully');\n } catch (error) {\n console.error('Error executing upload-only:', error);\n throw error;\n }\n }\n\n private async createEnvSecretFile(config: {\n mapStorageUrl: string;\n mapStorageApiKey: string;\n uploadDirectory: string;\n }): Promise<void> {\n const envSecretPath = path.join(process.cwd(), '.env.secret');\n\n // Create the .env.secret file with the provided configuration\n const secretContent = `# Secret configuration file for MAP_STORAGE upload mode\n# This file is not committed to git (see .gitignore)\n\nMAP_STORAGE_URL=${config.mapStorageUrl}\nMAP_STORAGE_API_KEY=${config.mapStorageApiKey}\nUPLOAD_DIRECTORY=${config.uploadDirectory}\n`;\n\n await fs.promises.writeFile(envSecretPath, secretContent, 'utf-8');\n }\n}\n","import express from 'express';\nimport * as path from \"node:path\";\nimport * as fs from \"node:fs\";\nimport cors from 'cors';\nimport { getCoreRoot } from './utils/getCoreRoot.ts';\nimport { FrontController } from './controllers/FrontController.ts';\nimport { MapController } from './controllers/MapController.ts';\nimport { UploaderController } from './controllers/UploaderController.ts';\n\nconst app = express();\n\nconst corsOptions = {\n credentials: true, // Allow sending cookies\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: [\n 'Content-Type',\n 'Authorization',\n 'X-Requested-With',\n 'Accept',\n 'Origin',\n 'Access-Control-Request-Method',\n 'Access-Control-Request-Headers'\n ],\n exposedHeaders: ['Content-Length', 'Content-Type'],\n maxAge: 86400, // Cache the OPTIONS requests for 24 hours\n};\n\n// Apply the CORS middleware\n// The CORS middleware automatically handles the OPTIONS requests (preflight)\napp.use(cors(corsOptions));\n\n// Parse JSON bodies\napp.use(express.json());\n\n// Configure the static assets for Express\nconst staticOptions = {\n maxAge: '1d', // Cache the files for 1 day\n etag: true, // Enable ETag for cache validation\n lastModified: true, // Enable Last-Modified header\n};\n\n// Serve dist/assets FIRST with explicit MIME type configuration\n// This ensures compiled JavaScript files from getMapsScripts are served correctly\n// This route must be before express.static('.') to take precedence\napp.use('/assets', express.static(path.join(process.cwd(), 'dist', 'assets'), staticOptions));\n// Serve the public folder from core (project root or package root for easy updates)\napp.use('/public', express.static(path.join(getCoreRoot(), 'public'), staticOptions));\n// Serve the tilesets folder with a longer cache (rarely modified)\napp.use('/tilesets', express.static(path.join(process.cwd(), 'tilesets'), {\n maxAge: '7d',\n etag: true,\n lastModified: true,\n}));\n\n// Middleware to exclude /src from express.static - let Vite handle TypeScript transformation\n// VitePluginNode will automatically add Vite middleware that transforms TypeScript files\nconst staticMiddleware = express.static('.', staticOptions);\n\n// Middleware to transform and serve TypeScript files as JavaScript\n// This bundles the file with its dependencies to resolve npm imports\napp.use('/src', async (req, res, next) => {\n // Only handle .ts and .tsx files - transform them to JavaScript\n if (req.path.endsWith('.ts') || req.path.endsWith('.tsx')) {\n try {\n // req.path includes /src/, so we need to join it correctly\n const filePath = path.join(process.cwd(), 'src', req.path.startsWith('/') ? req.path.slice(1) : req.path);\n \n // Check if file exists\n if (!fs.existsSync(filePath)) {\n return res.status(404).send('File not found');\n }\n \n // Use dynamic import to get esbuild (available via Vite)\n const esbuild = await import('esbuild');\n \n // Bundle the TypeScript file with its dependencies\n // This resolves npm imports like @workadventure/scripting-api-extra\n const result = await esbuild.build({\n entryPoints: [filePath],\n bundle: true,\n format: 'esm',\n target: 'esnext',\n write: false,\n platform: 'browser',\n sourcemap: false,\n // Externalize WorkAdventure global API (available in the browser)\n external: ['WA'],\n });\n \n res.setHeader('Content-Type', 'application/javascript; charset=utf-8');\n return res.send(result.outputFiles[0].text);\n } catch (error) {\n console.error('Error transforming TypeScript file:', error);\n return next(error);\n }\n }\n // For non-TypeScript files in /src, pass to next middleware\n next();\n});\n\n// Serve static files, but skip /src (handled above)\napp.use((req, res, next) => {\n // Skip /src requests - they are handled by the transformation middleware above\n if (req.path.startsWith('/src/')) {\n return next(); // Let the transformation middleware handle it or pass to Vite\n }\n // For other files, use express.static\n staticMiddleware(req, res, next);\n});\n\nconst controllers = [\n new MapController(app),\n new FrontController(app),\n new UploaderController(app),\n];\n\n// Verify and log all controllers created\ncontrollers.forEach(controller => {\n console.info(`Controller started: ${controller.constructor.name}`);\n});\n\nexport default app;\n// Export for VitePluginNode compatibility\nexport const viteNodeApp = app;\n"],"names":["path","fileURLToPath","fs","app","coreRoot","promisify","exec"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workadventure/map-starter-kit-core",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Core app, HTML pages and static assets for the WorkAdventure Map Starter Kit. Update this package to get new UI and server features without touching your maps or config.",
5
5
  "main": "src/server.ts",
6
6
  "scripts": {
@@ -31,7 +31,6 @@
31
31
  },
32
32
  "files": [
33
33
  "dist",
34
- "types",
35
34
  "public",
36
35
  "README.md"
37
36
  ],
@@ -1,3 +1,5 @@
1
+ import Mustache from 'https://cdn.jsdelivr.net/npm/mustache@4.2.0/+esm';
2
+
1
3
  // Function to retrieve the list of maps from the API
2
4
  async function getMapsList() {
3
5
  try {
@@ -74,7 +76,90 @@ async function createBackgroundImageFade(images = null) {
74
76
  }, 5000); // Change image every 5 seconds
75
77
  }
76
78
 
77
- // Exporter la fonction pour qu'elle soit accessible depuis index.html
78
- // Use a global declaration for TypeScript/JavaScript
79
- window.getMapsList = getMapsList;
80
- window.createBackgroundImageFade = createBackgroundImageFade;
79
+ // Mustache template for a map card (rendered in loadTMJ)
80
+ const CARD_TEMPLATE = `
81
+ <div class="map-cover" style="background-image: url('{{mapImageUrl}}');"></div>
82
+ <div class="map-date">
83
+ Last edit: {{lastModifiedFormatted}}
84
+ </div>
85
+ <div class="map-name">
86
+ {{mapName}}
87
+ </div>
88
+ <div class="map-detail">
89
+ <div class="map-file">
90
+ <strong>{{filename}}</strong>.tmj
91
+ </div>
92
+ <div class="map-weight">
93
+ <strong>{{size}}</strong>
94
+ <span style="opacity: .5">Mo</span>
95
+ </div>
96
+ </div>
97
+ <div class="map-desc">
98
+ {{mapDescription}}
99
+ </div>
100
+ <div class="map-testurl">
101
+ <a href="#" class="btn" data-map-path="{{path}}">Test my map</a>
102
+ </div>
103
+ `;
104
+
105
+ // Load maps from API and render map cards with Mustache
106
+ async function loadTMJ() {
107
+ try {
108
+ const maps = await getMapsList();
109
+
110
+ const mapImages = maps
111
+ .map((map) => {
112
+ if (map.mapImage) {
113
+ return map.mapImage.startsWith('http') ? map.mapImage : `/${map.mapImage}`;
114
+ }
115
+ return null;
116
+ })
117
+ .filter((img) => img !== null);
118
+
119
+ if (mapImages.length > 0) {
120
+ await createBackgroundImageFade(mapImages);
121
+ }
122
+
123
+ const defaultPlaceholder = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1620" height="1024"><rect fill="%231b2a41" width="100%" height="100%"/></svg>';
124
+ const mapsData = maps.map((map) => {
125
+ const mapImageUrl = map.mapImage
126
+ ? (map.mapImage.startsWith('http') ? map.mapImage : `/${map.mapImage}`)
127
+ : (mapImages.length > 0 ? mapImages[0] : defaultPlaceholder);
128
+ return {
129
+ ...map,
130
+ mapImageUrl,
131
+ mapDescription: map.mapDescription || 'No description available',
132
+ };
133
+ });
134
+
135
+ const mainElement = document.querySelector('main');
136
+ if (!mainElement) return;
137
+
138
+ mainElement.innerHTML = '';
139
+ mapsData.forEach((map) => {
140
+ const section = document.createElement('section');
141
+ section.className = 'card-map';
142
+ section.innerHTML = Mustache.render(CARD_TEMPLATE, map);
143
+
144
+ const testBtn = section.querySelector('.map-testurl a');
145
+ if (testBtn) {
146
+ testBtn.addEventListener('click', (e) => {
147
+ e.preventDefault();
148
+ const host = window.location.host;
149
+ let path = window.location.pathname;
150
+ if (path.endsWith('index.html')) {
151
+ path = path.slice(0, -'index.html'.length);
152
+ }
153
+ const instanceId = Math.random().toString(36).substring(2, 15);
154
+ const url = `https://play.workadventu.re/_/${instanceId}/${host}${path}${map.path}`;
155
+ window.open(url, '_blank');
156
+ });
157
+ }
158
+ mainElement.appendChild(section);
159
+ });
160
+ } catch (error) {
161
+ console.error('Error loading maps:', error);
162
+ }
163
+ }
164
+
165
+ export { getMapsList, getImagesList, createBackgroundImageFade, loadTMJ };
@@ -13,108 +13,11 @@
13
13
  <title>WorkAdventure build your map</title>
14
14
  <link rel="icon" href="public/images/favicon.svg" type="image/svg+xml">
15
15
  <script type="module">
16
- document.addEventListener("DOMContentLoaded", (event) => {
17
- // Load index.js to have access to getMapsList
18
- import('/public/assets/js/index.js').then(() => {
19
- loadTMJ();
16
+ document.addEventListener("DOMContentLoaded", () => {
17
+ import('/public/assets/js/index.js').then((module) => {
18
+ module.loadTMJ();
20
19
  });
21
20
  });
22
-
23
- async function loadTMJ() {
24
- try {
25
- // Get the list of maps from the API
26
- const maps = await window.getMapsList();
27
-
28
- // Retrieve map images for background fade
29
- const mapImages = maps
30
- .map(map => {
31
- if (map.mapImage) {
32
- return map.mapImage.startsWith('http') ? map.mapImage : `/${map.mapImage}`;
33
- }
34
- return null;
35
- })
36
- .filter(img => img !== null);
37
-
38
- // Create background image fade
39
- if (mapImages.length > 0) {
40
- await window.createBackgroundImageFade(mapImages);
41
- }
42
-
43
- // Mustache template for a map card
44
- const cardTemplate = `
45
- <div class="map-cover" style="background-image: url('{{mapImageUrl}}');"></div>
46
- <div class="map-date">
47
- Last edit: {{lastModifiedFormatted}}
48
- </div>
49
- <div class="map-name">
50
- {{mapName}}
51
- </div>
52
- <div class="map-detail">
53
- <div class="map-file">
54
- <strong>{{filename}}</strong>.tmj
55
- </div>
56
- <div class="map-weight">
57
- <strong>{{size}}</strong>
58
- <span style="opacity: .5">Mo</span>
59
- </div>
60
- </div>
61
- <div class="map-desc">
62
- {{mapDescription}}
63
- </div>
64
- <div class="map-testurl">
65
- <a href="#" class="btn" data-map-path="{{path}}">Test my map</a>
66
- </div>
67
- `;
68
-
69
- // Prepare data for Mustache
70
- const mapsData = maps.map(map => {
71
- // Build the image URL - use the first available image or a default image
72
- const mapImageUrl = map.mapImage
73
- ? (map.mapImage.startsWith('http') ? map.mapImage : `/${map.mapImage}`)
74
- : (mapImages.length > 0 ? mapImages[0] : 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1620" height="1024"><rect fill="%231b2a41" width="100%" height="100%"/></svg>');
75
-
76
- return {
77
- ...map,
78
- mapImageUrl: mapImageUrl,
79
- mapDescription: map.mapDescription || 'No description available'
80
- };
81
- });
82
-
83
- // Render each map with Mustache and inject them into the container
84
- const mainElement = document.querySelector('main');
85
- if (mainElement) {
86
- // Clear existing content
87
- mainElement.innerHTML = '';
88
-
89
- // Create a section for each map
90
- mapsData.forEach(map => {
91
- const section = document.createElement('section');
92
- section.className = 'card-map';
93
- section.innerHTML = Mustache.render(cardTemplate, map);
94
-
95
- // Add an event handler for the "Test my map" button
96
- const testBtn = section.querySelector('.map-testurl a');
97
- if (testBtn) {
98
- testBtn.addEventListener('click', (e) => {
99
- e.preventDefault();
100
- const host = window.location.host;
101
- let path = window.location.pathname;
102
- if (path.endsWith('index.html')) {
103
- path = path.substr(0, path.length - 'index.html'.length);
104
- }
105
- const instanceId = Math.random().toString(36).substring(2, 15);
106
- const url = `https://play.workadventu.re/_/${instanceId}/${host}${path}${map.path}`;
107
- window.open(url, '_blank');
108
- });
109
- }
110
-
111
- mainElement.appendChild(section);
112
- });
113
- }
114
- } catch (error) {
115
- console.error('Error loading maps:', error);
116
- }
117
- }
118
21
  </script>
119
22
  </head>
120
23
 
@@ -150,7 +53,7 @@
150
53
  </div>
151
54
  </header>
152
55
  <main>
153
- <!-- Map cards will be injected here dynamically by Mustache -->
56
+ <!-- Map cards are injected here by index.js -->
154
57
  </main>
155
58
  <div class="button-wrapper">
156
59
  <div style="flex-grow: 1;">
@@ -15,8 +15,8 @@
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", (event) => {
17
17
  // Load index.js to have access to getMapsList
18
- import('/public/assets/js/index.js').then(() => {
19
- window.createBackgroundImageFade();
18
+ import('/public/assets/js/index.js').then((module) => {
19
+ module.createBackgroundImageFade();
20
20
  });
21
21
  });
22
22
  </script>
@@ -47,8 +47,8 @@
47
47
  <script type="module">
48
48
  document.addEventListener("DOMContentLoaded", (event) => {
49
49
  // Load index.js to have access to getMapsList
50
- import('/public/assets/js/index.js').then(() => {
51
- window.createBackgroundImageFade();
50
+ import('/public/assets/js/index.js').then((module) => {
51
+ module.createBackgroundImageFade();
52
52
  });
53
53
  });
54
54
  </script>
@@ -14,8 +14,8 @@
14
14
  <link rel="icon" href="public/images/favicon.svg" type="image/svg+xml">
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", (event) => {
17
- import('/public/assets/js/index.js').then(() => {
18
- window.createBackgroundImageFade();
17
+ import('/public/assets/js/index.js').then((module) => {
18
+ module.createBackgroundImageFade();
19
19
  });
20
20
  });
21
21
  </script>
@@ -15,8 +15,8 @@
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", (event) => {
17
17
  // Load index.js to have access to getMapsList
18
- import('/public/assets/js/index.js').then(() => {
19
- window.createBackgroundImageFade();
18
+ import('/public/assets/js/index.js').then((module) => {
19
+ module.createBackgroundImageFade();
20
20
  });
21
21
  });
22
22
  </script>
@@ -14,7 +14,7 @@
14
14
  <link rel="icon" href="public/images/favicon.svg" type="image/svg+xml">
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", async () => {
17
- await import('/public/assets/js/index.js');
17
+ const module = await import('/public/assets/js/index.js');
18
18
  const main = document.querySelector('main .maps-container');
19
19
  const emptyEl = document.getElementById('maps-empty');
20
20
  const errorEl = document.getElementById('maps-error');
@@ -84,8 +84,8 @@
84
84
 
85
85
  // Background fade from first map image if available
86
86
  const firstImg = maps[0]?.mapImage;
87
- if (firstImg && typeof window.createBackgroundImageFade === 'function') {
88
- window.createBackgroundImageFade([firstImg]);
87
+ if (firstImg && typeof module.createBackgroundImageFade === 'function') {
88
+ module.createBackgroundImageFade([firstImg]);
89
89
  }
90
90
  } catch (e) {
91
91
  loadingEl.style.display = 'none';
@@ -15,8 +15,8 @@
15
15
  <script type="module">
16
16
  document.addEventListener("DOMContentLoaded", (event) => {
17
17
  // Load index.js to have access to getMapsList
18
- import('/public/assets/js/index.js').then(() => {
19
- window.createBackgroundImageFade();
18
+ import('/public/assets/js/index.js').then((module) => {
19
+ module.createBackgroundImageFade();
20
20
  });
21
21
  });
22
22
  </script>
package/src/server.ts CHANGED
@@ -2,10 +2,10 @@ import express from 'express';
2
2
  import * as path from "node:path";
3
3
  import * as fs from "node:fs";
4
4
  import cors from 'cors';
5
- import { getCoreRoot } from './getCoreRoot.js';
6
- import { FrontController } from './controllers/FrontController.js';
7
- import { MapController } from './controllers/MapController.js';
8
- import { UploaderController } from './controllers/UploaderController.js';
5
+ import { getCoreRoot } from './utils/getCoreRoot.ts';
6
+ import { FrontController } from './controllers/FrontController.ts';
7
+ import { MapController } from './controllers/MapController.ts';
8
+ import { UploaderController } from './controllers/UploaderController.ts';
9
9
 
10
10
  const app = express();
11
11
 
package/types/server.d.ts DELETED
@@ -1,5 +0,0 @@
1
- declare module "@workadventure/map-starter-kit-core/dist/server.js" {
2
- import type { Application } from "express";
3
- const core: { default: Application; viteNodeApp: Application };
4
- export default core;
5
- }