feat: 3615 Jai pete fork — patched API URLs, bypassed features, local mode

All api.opendaw.studio → /api/opendaw (local proxy)
Feature checks bypassed (crossOriginIsolated via Express headers)
DemoProjects, PublishMusic, UserCounter, ErrorsPage all local
This commit is contained in:
kxkm
2026-03-21 14:40:54 +01:00
parent 6203b36dd0
commit acffe78750
18 changed files with 172 additions and 191 deletions
+73
View File
@@ -0,0 +1,73 @@
const http = require("http");
const { execFileSync } = require("child_process");
const fs = require("fs");
const PORT = 8301;
const TTS_URL = "http://127.0.0.1:9100";
const OLLAMA_URL = "http://127.0.0.1:11434";
function readBody(req) {
return new Promise(r => { const c = []; req.on("data", d => c.push(d)); req.on("end", () => r(Buffer.concat(c).toString())); });
}
const server = http.createServer(async (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.method === "OPTIONS") { res.writeHead(200); res.end(); return; }
const url = new URL(req.url, `http://localhost:${PORT}`);
if (url.pathname === "/health") {
res.writeHead(200, {"Content-Type":"application/json"});
res.end(JSON.stringify({ok:true,service:"ai-bridge",backends:["tts","musicgen","noise","ollama"]}));
return;
}
if (url.pathname === "/generate/music" && req.method === "POST") {
const {prompt="ambient",duration=30,style="experimental"} = JSON.parse(await readBody(req));
try {
const r = await fetch(`${TTS_URL}/compose`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt:`${prompt}, ${style}`,duration}),signal:AbortSignal.timeout(300000)});
if (!r.ok) throw new Error(`TTS ${r.status}`);
const buf = Buffer.from(await r.arrayBuffer());
res.writeHead(200,{"Content-Type":"audio/wav","Content-Length":buf.length}); res.end(buf);
} catch(e) { res.writeHead(500,{"Content-Type":"application/json"}); res.end(JSON.stringify({error:e.message})); }
return;
}
if (url.pathname === "/generate/voice" && req.method === "POST") {
const {text="Bonjour",persona="pharmacius"} = JSON.parse(await readBody(req));
try {
const r = await fetch(`${TTS_URL}/synthesize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text,persona}),signal:AbortSignal.timeout(30000)});
if (!r.ok) throw new Error(`TTS ${r.status}`);
const buf = Buffer.from(await r.arrayBuffer());
res.writeHead(200,{"Content-Type":"audio/wav","Content-Length":buf.length}); res.end(buf);
} catch(e) { res.writeHead(500,{"Content-Type":"application/json"}); res.end(JSON.stringify({error:e.message})); }
return;
}
if (url.pathname === "/generate/noise" && req.method === "POST") {
const {type="pink",duration=10} = JSON.parse(await readBody(req));
const types = {white:"anoisesrc=d=DUR:c=white",pink:"anoisesrc=d=DUR:c=pink",brown:"anoisesrc=d=DUR:c=brown",sine:"sine=frequency=220:duration=DUR",drone:"sine=frequency=55:duration=DUR,tremolo=f=0.1:d=0.7"};
const filter = (types[type]||types.pink).replace(/DUR/g,String(duration));
const tmp = `/tmp/ai-noise-${Date.now()}.wav`;
try {
execFileSync("ffmpeg",["-f","lavfi","-i",filter,"-t",String(duration),"-ar","44100","-ac","2","-y",tmp],{timeout:15000});
const buf = fs.readFileSync(tmp); fs.unlinkSync(tmp);
res.writeHead(200,{"Content-Type":"audio/wav","Content-Length":buf.length}); res.end(buf);
} catch(e) { res.writeHead(500,{"Content-Type":"application/json"}); res.end(JSON.stringify({error:e.message})); }
return;
}
if (url.pathname === "/generate/suggest" && req.method === "POST") {
const {tracks=[]} = JSON.parse(await readBody(req));
const list = tracks.map((t,i)=>`#${i+1} [${t.type}] ${t.name} (${t.duration}s)`).join("\n");
try {
const r = await fetch(`${OLLAMA_URL}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:"qwen3.5:9b",messages:[{role:"user",content:`Compositeur. Pistes:\n${list}\nSuggere en JSON: {"type":"music|voice|noise","prompt":"...","duration":30}`}],stream:false,options:{num_predict:100},keep_alive:"30m",think:false}),signal:AbortSignal.timeout(15000)});
const d = await r.json();
res.writeHead(200,{"Content-Type":"application/json"}); res.end(d.message?.content||'{"type":"noise","prompt":"pink","duration":10}');
} catch { res.writeHead(200,{"Content-Type":"application/json"}); res.end('{"type":"noise","prompt":"pink","duration":10}'); }
return;
}
if (url.pathname === "/personas") {
try { const r = await fetch("http://localhost:3333/api/personas"); const d = await r.json(); res.writeHead(200,{"Content-Type":"application/json"}); res.end(JSON.stringify(d)); }
catch { res.writeHead(200,{"Content-Type":"application/json"}); res.end('{"data":[]}'); }
return;
}
res.writeHead(404); res.end("Not found");
});
server.listen(PORT, () => console.log(`[ai-bridge] :${PORT}`));
+19
View File
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUVin7hraN4CoyLlfzFCwtKgQiLZEwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDMyMTEyMTI0NFoXDTI3MDMy
MTEyMTI0NFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAnzO332ld3+3A7lGiX29bryZAJDzmEJFVdlH93/2AJ4eQ
iTSgeYVg4Wn5ynTm34tCFORNEERF5sgZ+YYWzYITM/cXySF4vFErs3aROGGBGl8C
jqDuL1FYs9UL9IUjVD5eNGVnrAKu34fi+OAkopN+l3jSr7SL9Y6eJ+Dx358PvQnJ
dDVCOa/vEj38z1f+QLJ2KiEtdMiLhlKOWcX6Mvzx0VQcf0rMK3R8IporBBQGY3s1
8lYgLJ1cBuR8zKR1cdHhvltz0xG06UvvvoTA6RJDHJeYmoE2qpwfdtg01+sl1ZKG
YtGQn+nP5wLmLWTiIuoaje5Aq7MJTWYDRG55p2hl9QIDAQABo1MwUTAdBgNVHQ4E
FgQUG+YDZltxNukp8Xv1y+c9P58fCKEwHwYDVR0jBBgwFoAUG+YDZltxNukp8Xv1
y+c9P58fCKEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEASprF
lqq2v84HBTTJtjrk4Wj9dhEWOlQ/uN03kcW3/kfv+cTAy0oDS1WsBVD7V+Z0TtPc
H40APr/nVwpOwvFgkJSYzEmIdTJGm6XO6DZc9vxzScUWernbJiRrrq6trpSjHtHA
7mVCHDDBy0QZusyF3G2qKpMtprpE9bgts5ctnCtPmHw0KS8OANA5s+tdhFVQ5Cl8
KMqlw9Yk2gN61ASj0Kdy8U6RdCruTWcU7yO3dCnA2v5NtMVxaZx5Qwgt7lW8CFWD
VNmgoQVPYvTNeSh6RmCSlnFI07Frl2yIWzpVK0ak/izuy54LtPt+lhjEQBnKVzMT
L6G6H6UmUlV9KI+JCQ==
-----END CERTIFICATE-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfM7ffaV3f7cDu
UaJfb1uvJkAkPOYQkVV2Uf3f/YAnh5CJNKB5hWDhafnKdObfi0IU5E0QREXmyBn5
hhbNghMz9xfJIXi8USuzdpE4YYEaXwKOoO4vUViz1Qv0hSNUPl40ZWesAq7fh+L4
4CSik36XeNKvtIv1jp4n4PHfnw+9Ccl0NUI5r+8SPfzPV/5AsnYqIS10yIuGUo5Z
xfoy/PHRVBx/SswrdHwimisEFAZjezXyViAsnVwG5HzMpHVx0eG+W3PTEbTpS+++
hMDpEkMcl5iagTaqnB922DTX6yXVkoZi0ZCf6c/nAuYtZOIi6hqN7kCrswlNZgNE
bnmnaGX1AgMBAAECggEAGqrtKA1cXkBTSxh9nILo1sQPhoJ0/1149WLypeyu5PA+
c91O7Ew9WEUKRLGkYtnO7fT4O7tUAj6Oq0+czjv1v4SqhVSIBvOJ4HwCA3HB0IsN
wJqQi3iS5TzQzdrnehsgaKJ75UJlzKfUi1/wgxM152Wj1S7nKvEC6SczHQRl/II9
gIpUbWK4EqB/3n2wuqjKRK8t+wOHAWE4aR+2bNgMF4OeOcnVZzANrxIgcrLXb1VX
rzJxPq3GzgyrONtOJR83eC8strrtY6HHx4h5U3zHqFeQZaOZZxNNuZ4dWbtUU6YR
Ui1RH/iD1oZwmrN1Kvj+16pscVlD5h4YaUlEqWkigQKBgQDRcuGymYjemM9Vm0PS
2beEo5/Yan2Qm5nQoBlxfTt8R7n33dC+Rq8OjszGIQ1pzmEMnCUTjppAhpo8Orh/
8tYenRKfDnReB88mdFwRdtRT9QpoxlUnhgefr9Nt0pceJKFW5mf3rItPaoZbWDDi
VTr382T7jsqrfkltaLwB8we70QKBgQDClevcTmowq5onz1lyPQQ/5AdbPRgM2gUM
BhC5eyqbjuz6w1XJxvtSdDt5A/gmiSUBKww9s6Dx50fYEEzlxNEHANsFyZ0Cl3HL
E/V0up9t2IRa2SmcEcSohKfNyXmSptvlRy7NY6MQtzK7xuIyrSBg2deS6WDx8rJ4
Tbx/AtQk5QKBgGvbojiL8jzXKmYmR74MGgJ3FHipFjJivsn9uDyGRhzQ/waSts1V
rw5rIS6ilLq46adJvZodpHe4GTMM9jgFgJhRQOah1fEaSmW+JC9mJx95H7wm7nEl
B0YHkHSMrv9504/hpQaZV0A1Vi0x9DKJu402TPlyZhH8PanW9oJ2ebQRAoGBAI13
LXiHUdB8kaBrsN3K2nIQuABvFDMTtiOdrT8L+lgJuuQ8avw9O64gbrR2XEy33D1T
LJ0XIOV9fc/pWd6aDlWHG7TDdPDTZZl8szDgFNRpWyApTYT3rf/OIakM1uP1h1zT
OYV88KpQZgP6fazWDPocpgtavG5qQbIQ4f988Pq5AoGAbxyCVTH//OrqfDNSlNTM
ta4TT1A4sDtSizCFHvfEkAoPB8PeMTMG2PC59JYQ7b6bL+ABabyTG1WAMh9EmrxZ
LH3TzVWmFXO/lxPAe1BT7mR/Z+EWxn+EWbzgAEdsXfhL3thMfYSu9de+zVZONfCO
KLPp3E3Yd+RI+6ssiQSBbA0=
-----END PRIVATE KEY-----
@@ -178,7 +178,7 @@ export class ErrorHandler {
error: {...error, stack: error.stack?.slice(0, maxStackSize)},
logs: LogBuffer.get()
} satisfies ErrorLog)
fetch("https://logs.opendaw.studio/log.php", {
fetch("/api/opendaw-logs/log.php", {
method: "POST",
headers: {"Content-Type": "application/json"},
body
+2 -12
View File
@@ -1,14 +1,4 @@
import {requireProperty} from "@opendaw/lib-std"
export const testFeatures = async (): Promise<void> => {
requireProperty(Promise, "withResolvers")
requireProperty(Array.prototype, "toSorted")
requireProperty(window, "indexedDB")
requireProperty(window, "AudioWorkletNode")
requireProperty(window, "SharedArrayBuffer")
requireProperty(navigator, "storage")
requireProperty(navigator.storage, "getDirectory")
requireProperty(crypto, "randomUUID")
requireProperty(crypto, "subtle")
requireProperty(crypto.subtle, "digest")
// Feature checks bypassed for 3615 J'ai pete
console.log("[3615] Feature checks bypassed")
}
-2
View File
@@ -19,7 +19,6 @@ import {UsersPage} from "@/ui/pages/UsersPage"
import {PrivacyPage} from "@/ui/pages/PrivacyPage"
import {PreferencesPage} from "@/ui/pages/PreferencesPage"
import {TestPage} from "@/ui/pages/TestPage"
import {AIPanel} from "@/ui/ai-panel/AIPanel"
export const App = (service: StudioService) => {
const terminator = new Terminator()
@@ -55,7 +54,6 @@ export const App = (service: StudioService) => {
]}
/>
<Footer lifecycle={terminator} service={service}/>
<AIPanel />
</Frag>
)
}
+1 -1
View File
@@ -136,7 +136,7 @@ export const Footer = ({lifecycle, service}: Construct) => {
}}/>
<article title="Users"
onInit={element => {
const counter = new UserCounter("https://api.opendaw.studio/users/user-counter.php")
const counter = new UserCounter("/api/opendaw/users/user-counter.php")
counter.subscribe(count => element.textContent = String(count))
}}>#
</article>
@@ -1,68 +0,0 @@
.AIPanel
background: #1a1a1a
border-top: 1px solid #333
padding: 8px 12px
font-family: "SF Mono", monospace
font-size: 11px
.ai-header
color: #4a9eff
font-weight: bold
margin-bottom: 6px
font-size: 12px
.ai-prompt
width: 100%
background: #242424
border: 1px solid #444
color: #ddd
font-family: inherit
font-size: 11px
padding: 6px 8px
border-radius: 3px
resize: none
box-sizing: border-box
&:focus
outline: none
border-color: #4a9eff
.ai-buttons
display: flex
gap: 4px
margin: 6px 0
flex-wrap: wrap
.ai-btn
background: #333
border: 1px solid #555
color: #bbb
font-family: inherit
font-size: 10px
padding: 4px 10px
border-radius: 3px
cursor: pointer
transition: all 0.1s
&:hover
background: #444
color: #fff
&:active
transform: scale(0.97)
.ai-btn-accent
background: #2a5a99
border-color: #4a9eff
color: #ddd
&:hover
background: #4a9eff
color: #fff
.ai-status
color: #888
font-size: 10px
min-height: 14px
margin: 4px 0
.ai-audio
width: 100%
height: 28px
margin-top: 4px
@@ -1,95 +0,0 @@
import css from "./AIPanel.sass?inline"
import {createElement} from "@opendaw/lib-jsx"
import {Html} from "@opendaw/lib-dom"
const className = Html.adoptStyleSheet(css, "AIPanel")
const AI_BRIDGE = "http://localhost:8301"
type Construct = {}
export const AIPanel = ({}: Construct) => {
let promptInput: HTMLTextAreaElement
let statusEl: HTMLElement
let resultAudio: HTMLAudioElement
async function generateMusic() {
const prompt = promptInput.value.trim()
if (!prompt) return
statusEl.textContent = "Generating music..."
try {
const resp = await fetch(`${AI_BRIDGE}/generate/music`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({prompt, duration: 30, style: "experimental"})
})
if (!resp.ok) throw new Error(`${resp.status}`)
const blob = await resp.blob()
resultAudio.src = URL.createObjectURL(blob)
statusEl.textContent = "Music generated!"
} catch(e) { statusEl.textContent = `Error: ${e}` }
}
async function generateVoice() {
const text = promptInput.value.trim()
if (!text) return
statusEl.textContent = "Generating voice..."
try {
const resp = await fetch(`${AI_BRIDGE}/generate/voice`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({text, persona: "pharmacius"})
})
if (!resp.ok) throw new Error(`${resp.status}`)
const blob = await resp.blob()
resultAudio.src = URL.createObjectURL(blob)
statusEl.textContent = "Voice generated!"
} catch(e) { statusEl.textContent = `Error: ${e}` }
}
async function generateNoise(type: string) {
statusEl.textContent = `Generating ${type}...`
try {
const resp = await fetch(`${AI_BRIDGE}/generate/noise`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({type, duration: 10})
})
if (!resp.ok) throw new Error(`${resp.status}`)
const blob = await resp.blob()
resultAudio.src = URL.createObjectURL(blob)
statusEl.textContent = `${type} noise generated!`
} catch(e) { statusEl.textContent = `Error: ${e}` }
}
async function suggest() {
statusEl.textContent = "AI thinking..."
try {
const resp = await fetch(`${AI_BRIDGE}/generate/suggest`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({tracks: []})
})
const data = await resp.json()
promptInput.value = typeof data === "string" ? data : JSON.stringify(data)
statusEl.textContent = "Suggestion ready"
} catch(e) { statusEl.textContent = `Error: ${e}` }
}
return (
<div className={className}>
<div className="ai-header">AI GENERATION</div>
<textarea onInit={(el: HTMLTextAreaElement) => promptInput = el} className="ai-prompt" placeholder="Describe your sound..." rows={2} />
<div className="ai-buttons">
<button className="ai-btn ai-btn-accent" onclick={generateMusic}>MUSIC</button>
<button className="ai-btn" onclick={generateVoice}>VOICE</button>
<button className="ai-btn" onclick={() => generateNoise("drone")}>DRONE</button>
<button className="ai-btn" onclick={() => generateNoise("pink")}>PINK</button>
<button className="ai-btn" onclick={() => generateNoise("white")}>WHITE</button>
<button className="ai-btn" onclick={suggest}>SUGGEST</button>
</div>
<div onInit={(el: HTMLElement) => statusEl = el} className="ai-status"></div>
<audio onInit={(el: HTMLAudioElement) => resultAudio = el} controls className="ai-audio" />
</div>
)
}
@@ -13,7 +13,7 @@ type Construct = {
export const DemoProject = ({json, load}: Construct) => {
const coverUrl = json.hasCover
? `https://api.opendaw.studio/music/cover.php?id=${json.id}&preview=true`
? `/api/opendaw/music/cover.php?id=${json.id}&preview=true`
: "./empty.svg"
return (
<div className={className} onclick={load}>
@@ -38,7 +38,7 @@ const ids = [
"cab976763f0" // Vapor Run
]
const listUrl = `https://api.opendaw.studio/music/list-by-ids.php?ids=${ids.join(",")}`
const listUrl = `/api/opendaw/music/list-by-ids.php?ids=${ids.join(",")}`
const NewProjectJson: DemoProjectJson = {
id: "",
@@ -71,7 +71,7 @@ const loadDemoProject = async (service: StudioService, json: DemoProjectJson) =>
const dialog = RuntimeNotifier.progress({headline: "Loading Demo Project"})
const folder = json.id
const {status, value: arrayBuffer, error} = await Promises.tryCatch(
fetch(`https://api.opendaw.studio/music/uploads/${folder}/project.odb`)
fetch(`/api/opendaw/music/uploads/${folder}/project.odb`)
.then(network.progress(progress => dialog.message = `Downloading bundle file... (${(progress * 100).toFixed(1)}%)`))
.then(x => x.arrayBuffer()))
dialog.terminate()
@@ -54,7 +54,7 @@ export namespace PublishMusic {
})
xhr.addEventListener("error", () => reject(new Error("Network error")))
xhr.addEventListener("abort", () => reject(new Error("Upload cancelled")))
xhr.open("POST", "https://api.opendaw.studio/music/upload.php")
xhr.open("POST", "/api/opendaw/music/upload.php")
xhr.send(formData)
return promise
}
@@ -62,7 +62,7 @@ export namespace PublishMusic {
export const deleteMusic = async (token: string): Promise<void> => {
const formData = new FormData()
formData.append("token", token)
const response = await fetch("https://api.opendaw.studio/music/delete.php", {
const response = await fetch("/api/opendaw/music/delete.php", {
method: "POST",
body: formData
})
@@ -11,7 +11,7 @@ const loadLimit = 100
export const ErrorsPage: PageFactory<StudioService> = ({lifecycle}: PageContext<StudioService>) => {
let offset = 0
const loadMore = () => fetch(`https://logs.opendaw.studio/list.php?offset=${offset}&limit=${loadLimit}`)
const loadMore = () => fetch(`/api/opendaw-logs/list.php?offset=${offset}&limit=${loadLimit}`)
.then(response => {
offset += loadLimit
return response.json()
@@ -28,7 +28,7 @@ export const ErrorsPage: PageFactory<StudioService> = ({lifecycle}: PageContext<
>here</a>.</h5>
<code onInit={async element => {
element.textContent = "loading status..."
element.textContent = await fetch("https://logs.opendaw.studio/status.php").then(x => x.json())
element.textContent = await fetch("/api/opendaw-logs/status.php").then(x => x.json())
.then(x => Object.entries(x).map(([key, value]) => `${key}: ${value}`).join(", "))
}} style={{fontSize: "10px", marginBottom: "1em", color: Colors.blue.toString()}}/>
<Await
@@ -15,7 +15,7 @@ export const OpenBundlePage: PageFactory<StudioService> = ({service, path}: Page
const dialog = RuntimeNotifier.progress({headline: "Loading bundle file..."})
const folder = path.substring(path.lastIndexOf("/") + 1)
const {status, value: arrayBuffer, error} = await Promises.tryCatch(
fetch(`https://api.opendaw.studio/music/uploads/${folder}/project.odb`)
fetch(`/api/opendaw/music/uploads/${folder}/project.odb`)
.then(network.progress(progress => message.textContent = `Downloading Bundle... (${(progress * 100).toFixed(1)}%)`))
.then(x => x.arrayBuffer()))
dialog.terminate()
@@ -14,7 +14,7 @@ export const UsersPage: PageFactory<StudioService> = ({lifecycle}: PageContext<S
<div className={className}>
<h1>Peak Concurrent Users</h1>
<Await
factory={() => fetch("https://api.opendaw.studio/users/graph.json", {
factory={() => fetch("/api/opendaw/users/graph.json", {
mode: "cors",
credentials: "include"
})
@@ -19,7 +19,7 @@ import {WavFile} from "../WavFile"
// Standard openDAW samples (considered to be non-removable)
export class OpenSampleAPI implements SampleAPI {
static readonly ApiRoot = "https://api.opendaw.studio/samples"
static readonly ApiRoot = "/api/opendaw/samples"
static readonly FileRoot = "https://assets.opendaw.studio/samples"
@Lazy
@@ -5,7 +5,7 @@ import {network, Promises} from "@opendaw/lib-runtime"
import {z} from "zod"
export class OpenSoundfontAPI {
static readonly ApiRoot = "https://api.opendaw.studio/soundfonts"
static readonly ApiRoot = "/api/opendaw/soundfonts"
static readonly FileRoot = "https://assets.opendaw.studio/soundfonts"
@Lazy
+36
View File
@@ -0,0 +1,36 @@
const https = require("https");
const fs = require("fs");
const path = require("path");
const PORT = 8300;
const DIST = path.join(__dirname, "packages/app/studio/dist");
const options = {
key: fs.readFileSync(path.join(__dirname, "key.pem")),
cert: fs.readFileSync(path.join(__dirname, "cert.pem")),
};
const MIME = {
".html": "text/html", ".js": "application/javascript", ".css": "text/css",
".svg": "image/svg+xml", ".png": "image/png", ".ico": "image/x-icon",
".woff2": "font/woff2", ".woff": "font/woff", ".ttf": "font/ttf",
".wasm": "application/wasm", ".json": "application/json",
".map": "application/json",
".mp3": "audio/mpeg", ".wav": "audio/wav", ".ogg": "audio/ogg",
};
https.createServer(options, (req, res) => {
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
res.setHeader("Access-Control-Allow-Origin", "*");
let urlPath = decodeURIComponent(new URL(req.url, "https://localhost").pathname);
let filePath = path.join(DIST, urlPath === "/" ? "index.html" : urlPath);
if (!fs.existsSync(filePath) && !path.extname(filePath)) filePath = path.join(DIST, "index.html");
if (!fs.existsSync(filePath)) { res.writeHead(404); res.end("Not found"); return; }
const ext = path.extname(filePath);
res.setHeader("Content-Type", MIME[ext] || "application/octet-stream");
res.writeHead(200);
fs.createReadStream(filePath).pipe(res);
}).listen(PORT, "0.0.0.0", () => console.log("[openDAW] HTTPS :" + PORT));