@xiboplayer/proxy 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/bin/cli.js +27 -0
- package/package.json +39 -0
- package/src/index.js +1 -0
- package/src/proxy.js +198 -0
- package/src/proxy.test.js +88 -0
- package/vitest.config.js +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @xiboplayer/proxy
|
|
2
|
+
|
|
3
|
+
**CORS proxy and static server for Xibo Player shells.**
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Shared Express server used by all XiboPlayer shells (Electron, Chromium kiosk, standalone):
|
|
8
|
+
|
|
9
|
+
- **XMDS Proxy** — forwards SOAP/XML requests to the CMS (`/xmds-proxy`)
|
|
10
|
+
- **REST Proxy** — forwards REST API requests (`/rest-proxy`)
|
|
11
|
+
- **File Proxy** — downloads media files with Range support (`/file-proxy`)
|
|
12
|
+
- **PWA Server** — serves the PWA player as static files (`/player/pwa/`)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @xiboplayer/proxy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### As a library
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import { createProxyApp, startServer } from '@xiboplayer/proxy';
|
|
26
|
+
|
|
27
|
+
// Option 1: get the Express app (for embedding in Electron, etc.)
|
|
28
|
+
const app = createProxyApp({
|
|
29
|
+
pwaPath: '/path/to/pwa/dist',
|
|
30
|
+
appVersion: '1.0.0',
|
|
31
|
+
});
|
|
32
|
+
app.listen(8765, 'localhost');
|
|
33
|
+
|
|
34
|
+
// Option 2: start a standalone server
|
|
35
|
+
const { server, port } = await startServer({
|
|
36
|
+
port: 8765,
|
|
37
|
+
pwaPath: '/path/to/pwa/dist',
|
|
38
|
+
appVersion: '1.0.0',
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### As a CLI
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx xiboplayer-proxy --pwa-path=../xiboplayer-pwa/dist --port=8765
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API Reference
|
|
49
|
+
|
|
50
|
+
### `createProxyApp({ pwaPath, appVersion })`
|
|
51
|
+
|
|
52
|
+
Returns a configured Express app with all proxy routes and static PWA serving.
|
|
53
|
+
|
|
54
|
+
| Parameter | Type | Default | Description |
|
|
55
|
+
|-----------|------|---------|-------------|
|
|
56
|
+
| `pwaPath` | `string` | (required) | Absolute path to PWA dist directory |
|
|
57
|
+
| `appVersion` | `string` | `'0.0.0'` | Version for User-Agent header |
|
|
58
|
+
|
|
59
|
+
### `startServer({ port, pwaPath, appVersion })`
|
|
60
|
+
|
|
61
|
+
Creates the app and starts listening. Returns `Promise<{ server, port }>`.
|
|
62
|
+
|
|
63
|
+
| Parameter | Type | Default | Description |
|
|
64
|
+
|-----------|------|---------|-------------|
|
|
65
|
+
| `port` | `number` | `8765` | Port to listen on |
|
|
66
|
+
| `pwaPath` | `string` | (required) | Absolute path to PWA dist directory |
|
|
67
|
+
| `appVersion` | `string` | `'0.0.0'` | Version for User-Agent header |
|
|
68
|
+
|
|
69
|
+
## Proxy Routes
|
|
70
|
+
|
|
71
|
+
| Route | Method | Description |
|
|
72
|
+
|-------|--------|-------------|
|
|
73
|
+
| `/xmds-proxy?cms=URL` | ALL | Proxies XMDS SOAP requests to `URL/xmds.php` |
|
|
74
|
+
| `/rest-proxy?cms=URL&path=/api/...` | ALL | Proxies REST API requests |
|
|
75
|
+
| `/file-proxy?cms=URL&url=/path` | GET | Downloads files (supports Range) |
|
|
76
|
+
| `/player/pwa/` | GET | Serves PWA static files |
|
|
77
|
+
| `/` | GET | Redirects to `/player/pwa/` |
|
|
78
|
+
|
|
79
|
+
## Dependencies
|
|
80
|
+
|
|
81
|
+
- `express` — HTTP server
|
|
82
|
+
- `cors` — CORS middleware
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
**Part of the [XiboPlayer SDK](https://github.com/xibo-players/xiboplayer)**
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* xiboplayer-proxy CLI — standalone CORS proxy + PWA server
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* xiboplayer-proxy --port=8765 --pwa-path=/path/to/pwa/dist
|
|
7
|
+
* npx xiboplayer-proxy --pwa-path=../xiboplayer-pwa/dist
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { startServer } from '../src/proxy.js';
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const portArg = args.find(a => a.startsWith('--port='));
|
|
14
|
+
const pwaArg = args.find(a => a.startsWith('--pwa-path='));
|
|
15
|
+
|
|
16
|
+
const port = portArg ? parseInt(portArg.split('=')[1], 10) : 8765;
|
|
17
|
+
const pwaPath = pwaArg ? pwaArg.split('=')[1] : null;
|
|
18
|
+
|
|
19
|
+
if (!pwaPath) {
|
|
20
|
+
console.error('Usage: xiboplayer-proxy --pwa-path=/path/to/pwa/dist [--port=8765]');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
startServer({ port, pwaPath, appVersion: '0.2.0' }).catch((err) => {
|
|
25
|
+
console.error('Failed to start:', err.message);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xiboplayer/proxy",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CORS proxy and static server for Xibo Player (serves PWA + proxies CMS)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./cli": "./bin/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"xiboplayer-proxy": "./bin/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"express": "^5.2.1",
|
|
16
|
+
"cors": "^2.8.6"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"vitest": "^2.0.0"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"xibo",
|
|
23
|
+
"digital-signage",
|
|
24
|
+
"proxy",
|
|
25
|
+
"cors",
|
|
26
|
+
"express"
|
|
27
|
+
],
|
|
28
|
+
"author": "Pau Aliagas <linuxnow@gmail.com>",
|
|
29
|
+
"license": "AGPL-3.0-or-later",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/xibo-players/xiboplayer.git",
|
|
33
|
+
"directory": "packages/proxy"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createProxyApp, startServer } from './proxy.js';
|
package/src/proxy.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xiboplayer/proxy — CORS proxy + static server for Xibo Player
|
|
3
|
+
*
|
|
4
|
+
* Provides Express middleware that:
|
|
5
|
+
* - Proxies XMDS SOAP requests (/xmds-proxy)
|
|
6
|
+
* - Proxies REST API requests (/rest-proxy)
|
|
7
|
+
* - Proxies file downloads with Range support (/file-proxy)
|
|
8
|
+
* - Serves the PWA player as static files (/player/pwa/)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import express from 'express';
|
|
13
|
+
import cors from 'cors';
|
|
14
|
+
|
|
15
|
+
const SKIP_HEADERS = ['transfer-encoding', 'connection', 'content-encoding', 'content-length'];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a configured Express app with CORS proxy routes and PWA static serving.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} options
|
|
21
|
+
* @param {string} options.pwaPath — absolute path to PWA dist directory
|
|
22
|
+
* @param {string} [options.appVersion='0.0.0'] — version string for User-Agent header
|
|
23
|
+
* @returns {import('express').Express}
|
|
24
|
+
*/
|
|
25
|
+
export function createProxyApp({ pwaPath, appVersion = '0.0.0' }) {
|
|
26
|
+
const app = express();
|
|
27
|
+
|
|
28
|
+
app.use(cors({
|
|
29
|
+
origin: '*',
|
|
30
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
31
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'SOAPAction'],
|
|
32
|
+
credentials: true,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
app.use(express.text({ type: 'text/xml', limit: '50mb' }));
|
|
36
|
+
app.use(express.text({ type: 'application/xml', limit: '50mb' }));
|
|
37
|
+
app.use(express.json({ limit: '10mb' }));
|
|
38
|
+
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
39
|
+
|
|
40
|
+
// ─── XMDS SOAP Proxy ──────────────────────────────────────────────
|
|
41
|
+
app.all('/xmds-proxy', async (req, res) => {
|
|
42
|
+
try {
|
|
43
|
+
const cmsUrl = req.query.cms;
|
|
44
|
+
if (!cmsUrl) return res.status(400).json({ error: 'Missing cms parameter' });
|
|
45
|
+
|
|
46
|
+
const queryParams = new URLSearchParams(req.query);
|
|
47
|
+
queryParams.delete('cms');
|
|
48
|
+
const queryString = queryParams.toString();
|
|
49
|
+
const xmdsUrl = `${cmsUrl}/xmds.php${queryString ? '?' + queryString : ''}`;
|
|
50
|
+
|
|
51
|
+
console.log(`[Proxy] ${req.method} ${xmdsUrl}`);
|
|
52
|
+
|
|
53
|
+
const headers = {
|
|
54
|
+
'Content-Type': req.headers['content-type'] || 'text/xml; charset=utf-8',
|
|
55
|
+
'User-Agent': `XiboPlayer/${appVersion}`,
|
|
56
|
+
};
|
|
57
|
+
if (req.headers['soapaction']) headers['SOAPAction'] = req.headers['soapaction'];
|
|
58
|
+
|
|
59
|
+
const response = await fetch(xmdsUrl, {
|
|
60
|
+
method: req.method,
|
|
61
|
+
headers,
|
|
62
|
+
body: req.method !== 'GET' && req.body ? req.body : undefined,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const contentType = response.headers.get('content-type');
|
|
66
|
+
if (contentType) res.setHeader('Content-Type', contentType);
|
|
67
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
68
|
+
const responseText = await response.text();
|
|
69
|
+
res.status(response.status).send(responseText);
|
|
70
|
+
console.log(`[Proxy] ${response.status} (${responseText.length} bytes)`);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('[Proxy] Error:', error.message);
|
|
73
|
+
res.status(500).json({ error: 'Proxy error', message: error.message });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ─── REST API Proxy ────────────────────────────────────────────────
|
|
78
|
+
app.all('/rest-proxy', async (req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const cmsUrl = req.query.cms;
|
|
81
|
+
const apiPath = req.query.path;
|
|
82
|
+
if (!cmsUrl) return res.status(400).json({ error: 'Missing cms parameter' });
|
|
83
|
+
|
|
84
|
+
const queryParams = new URLSearchParams(req.query);
|
|
85
|
+
queryParams.delete('cms');
|
|
86
|
+
queryParams.delete('path');
|
|
87
|
+
const queryString = queryParams.toString();
|
|
88
|
+
const fullUrl = `${cmsUrl}${apiPath || ''}${queryString ? '?' + queryString : ''}`;
|
|
89
|
+
|
|
90
|
+
console.log(`[REST Proxy] ${req.method} ${fullUrl}`);
|
|
91
|
+
|
|
92
|
+
const headers = { 'User-Agent': `XiboPlayer/${appVersion}` };
|
|
93
|
+
if (req.headers['content-type']) headers['Content-Type'] = req.headers['content-type'];
|
|
94
|
+
if (req.headers['authorization']) headers['Authorization'] = req.headers['authorization'];
|
|
95
|
+
if (req.headers['accept']) headers['Accept'] = req.headers['accept'];
|
|
96
|
+
if (req.headers['if-none-match']) headers['If-None-Match'] = req.headers['if-none-match'];
|
|
97
|
+
|
|
98
|
+
const fetchOptions = { method: req.method, headers };
|
|
99
|
+
if (req.method !== 'GET' && req.method !== 'HEAD' && req.body) {
|
|
100
|
+
fetchOptions.body = typeof req.body === 'string' ? req.body : JSON.stringify(req.body);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const response = await fetch(fullUrl, fetchOptions);
|
|
104
|
+
response.headers.forEach((value, key) => {
|
|
105
|
+
if (!SKIP_HEADERS.includes(key.toLowerCase())) {
|
|
106
|
+
res.setHeader(key, value);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
110
|
+
const buffer = await response.arrayBuffer();
|
|
111
|
+
res.status(response.status).send(Buffer.from(buffer));
|
|
112
|
+
console.log(`[REST Proxy] ${response.status} (${buffer.byteLength} bytes)`);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error('[REST Proxy] Error:', error.message);
|
|
115
|
+
res.status(500).json({ error: 'REST proxy error', message: error.message });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ─── File Download Proxy ───────────────────────────────────────────
|
|
120
|
+
app.get('/file-proxy', async (req, res) => {
|
|
121
|
+
try {
|
|
122
|
+
const cmsUrl = req.query.cms;
|
|
123
|
+
const fileUrl = req.query.url;
|
|
124
|
+
if (!cmsUrl || !fileUrl) return res.status(400).json({ error: 'Missing cms or url parameter' });
|
|
125
|
+
|
|
126
|
+
const fullUrl = `${cmsUrl}${fileUrl}`;
|
|
127
|
+
console.log(`[FileProxy] GET ${fullUrl}`);
|
|
128
|
+
|
|
129
|
+
const headers = { 'User-Agent': `XiboPlayer/${appVersion}` };
|
|
130
|
+
if (req.headers.range) {
|
|
131
|
+
headers['Range'] = req.headers.range;
|
|
132
|
+
console.log(`[FileProxy] Range: ${req.headers.range}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const response = await fetch(fullUrl, { headers });
|
|
136
|
+
res.status(response.status);
|
|
137
|
+
response.headers.forEach((value, key) => {
|
|
138
|
+
if (!SKIP_HEADERS.includes(key.toLowerCase())) {
|
|
139
|
+
res.setHeader(key, value);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
143
|
+
const buffer = await response.arrayBuffer();
|
|
144
|
+
res.send(Buffer.from(buffer));
|
|
145
|
+
console.log(`[FileProxy] ${response.status} (${buffer.byteLength} bytes)`);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error('[FileProxy] Error:', error.message);
|
|
148
|
+
res.status(500).json({ error: 'File proxy error', message: error.message });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ─── Serve PWA static files ────────────────────────────────────────
|
|
153
|
+
app.use('/player/pwa', express.static(pwaPath, {
|
|
154
|
+
setHeaders: (res, filePath) => {
|
|
155
|
+
if (filePath.endsWith('sw-pwa.js')) {
|
|
156
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
157
|
+
res.setHeader('Service-Worker-Allowed', '/player/pwa/');
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
app.get('/', (req, res) => res.redirect('/player/pwa/'));
|
|
163
|
+
app.get('/player/pwa/{*splat}', (req, res) => res.sendFile(path.join(pwaPath, 'index.html')));
|
|
164
|
+
|
|
165
|
+
return app;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create the proxy app and start listening.
|
|
170
|
+
*
|
|
171
|
+
* @param {object} options
|
|
172
|
+
* @param {number} [options.port=8765]
|
|
173
|
+
* @param {string} options.pwaPath
|
|
174
|
+
* @param {string} [options.appVersion='0.0.0']
|
|
175
|
+
* @returns {Promise<{ server: import('http').Server, port: number }>}
|
|
176
|
+
*/
|
|
177
|
+
export function startServer({ port = 8765, pwaPath, appVersion = '0.0.0' }) {
|
|
178
|
+
const app = createProxyApp({ pwaPath, appVersion });
|
|
179
|
+
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const server = app.listen(port, 'localhost', () => {
|
|
182
|
+
console.log(`[Server] Running on http://localhost:${port}`);
|
|
183
|
+
console.log(`[Server] READY`);
|
|
184
|
+
resolve({ server, port });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
server.on('error', (err) => {
|
|
188
|
+
if (err.code === 'EADDRINUSE') {
|
|
189
|
+
console.error(`[Server] Port ${port} already in use. Try --port=XXXX`);
|
|
190
|
+
}
|
|
191
|
+
reject(err);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Graceful shutdown
|
|
195
|
+
process.on('SIGINT', () => { server.close(); process.exit(0); });
|
|
196
|
+
process.on('SIGTERM', () => { server.close(); process.exit(0); });
|
|
197
|
+
});
|
|
198
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
3
|
+
import { createProxyApp } from './proxy.js';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
// Restore real fetch (root vitest.setup.js mocks it with vi.fn())
|
|
12
|
+
const realFetch = global.__nativeFetch || globalThis.fetch;
|
|
13
|
+
|
|
14
|
+
// Create a temporary PWA directory with a minimal index.html
|
|
15
|
+
let pwaPath;
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
global.fetch = realFetch;
|
|
18
|
+
pwaPath = fs.mkdtempSync(path.join(os.tmpdir(), 'proxy-test-pwa-'));
|
|
19
|
+
fs.writeFileSync(path.join(pwaPath, 'index.html'), '<html><body>test</body></html>');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function makeApp() {
|
|
23
|
+
return createProxyApp({ pwaPath, appVersion: '0.0.0-test' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Helper to make a request to the Express app without starting a persistent server
|
|
27
|
+
async function request(app, method, url) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const server = app.listen(0, 'localhost', () => {
|
|
30
|
+
const port = server.address().port;
|
|
31
|
+
realFetch(`http://localhost:${port}${url}`, { method })
|
|
32
|
+
.then(async (res) => {
|
|
33
|
+
const body = await res.text();
|
|
34
|
+
server.close();
|
|
35
|
+
resolve({ status: res.status, body, headers: res.headers });
|
|
36
|
+
})
|
|
37
|
+
.catch((err) => {
|
|
38
|
+
server.close();
|
|
39
|
+
resolve({ status: 0, body: err.message, headers: new Headers() });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('createProxyApp', () => {
|
|
46
|
+
it('serves PWA at /player/pwa/', async () => {
|
|
47
|
+
const app = makeApp();
|
|
48
|
+
const res = await request(app, 'GET', '/player/pwa/');
|
|
49
|
+
expect(res.status).toBe(200);
|
|
50
|
+
expect(res.body).toContain('test');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('redirects / to /player/pwa/', async () => {
|
|
54
|
+
const app = makeApp();
|
|
55
|
+
// fetch follows redirects by default, so we check the final body
|
|
56
|
+
const res = await request(app, 'GET', '/');
|
|
57
|
+
expect(res.status).toBe(200);
|
|
58
|
+
expect(res.body).toContain('test');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('returns 400 for /xmds-proxy without cms param', async () => {
|
|
62
|
+
const app = makeApp();
|
|
63
|
+
const res = await request(app, 'GET', '/xmds-proxy');
|
|
64
|
+
expect(res.status).toBe(400);
|
|
65
|
+
expect(res.body).toContain('Missing cms parameter');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns 400 for /rest-proxy without cms param', async () => {
|
|
69
|
+
const app = makeApp();
|
|
70
|
+
const res = await request(app, 'GET', '/rest-proxy');
|
|
71
|
+
expect(res.status).toBe(400);
|
|
72
|
+
expect(res.body).toContain('Missing cms parameter');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('returns 400 for /file-proxy without cms param', async () => {
|
|
76
|
+
const app = makeApp();
|
|
77
|
+
const res = await request(app, 'GET', '/file-proxy');
|
|
78
|
+
expect(res.status).toBe(400);
|
|
79
|
+
expect(res.body).toContain('Missing cms or url parameter');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('SPA fallback serves index.html for sub-routes', async () => {
|
|
83
|
+
const app = makeApp();
|
|
84
|
+
const res = await request(app, 'GET', '/player/pwa/some/deep/route');
|
|
85
|
+
expect(res.status).toBe(200);
|
|
86
|
+
expect(res.body).toContain('test');
|
|
87
|
+
});
|
|
88
|
+
});
|