@grame/faustwasm 0.8.2 → 0.9.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/assets/standalone/create-node.js +15 -8
- package/assets/standalone/faustwasm/index.d.ts +40 -0
- package/assets/standalone/faustwasm/index.js +109 -30
- package/assets/standalone/faustwasm/index.js.map +2 -2
- package/assets/standalone/index-pwa.js +1 -1
- package/assets/standalone/service-worker.js +58 -5
- package/dist/cjs/index.d.ts +40 -0
- package/dist/cjs/index.js +109 -30
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs-bundle/index.d.ts +40 -0
- package/dist/cjs-bundle/index.js +109 -30
- package/dist/cjs-bundle/index.js.map +2 -2
- package/dist/esm/index.d.ts +40 -0
- package/dist/esm/index.js +109 -30
- package/dist/esm/index.js.map +2 -2
- package/dist/esm-bundle/index.d.ts +40 -0
- package/dist/esm-bundle/index.js +109 -30
- package/dist/esm-bundle/index.js.map +2 -2
- package/package.json +1 -1
- package/src/FaustAudioWorkletNode.ts +10 -0
- package/src/FaustAudioWorkletProcessor.ts +16 -0
- package/src/FaustOfflineProcessor.ts +2 -0
- package/src/FaustScriptProcessorNode.ts +2 -0
- package/src/FaustWebAudioDsp.ts +88 -0
- package/src/copyWebStandaloneAssets.js +14 -0
- package/test/faustlive-wasm/faustwasm/index.d.ts +40 -0
- package/test/faustlive-wasm/faustwasm/index.js +109 -30
- package/test/faustlive-wasm/faustwasm/index.js.map +2 -2
- package/test/midi/create-node.js +177 -0
- package/test/midi/dsp-meta.json +133 -0
- package/test/midi/dsp-module.wasm +0 -0
- package/test/midi/faust-ui/index.css +442 -0
- package/test/midi/faust-ui/index.css.map +1 -0
- package/test/midi/faust-ui/index.d.ts +1 -0
- package/test/midi/faust-ui/index.js +3729 -0
- package/test/midi/faust-ui/index.js.map +1 -0
- package/test/midi/faustwasm/index.d.ts +1680 -0
- package/test/midi/faustwasm/index.js +4884 -0
- package/test/midi/faustwasm/index.js.map +7 -0
- package/test/midi/icon.png +0 -0
- package/test/midi/index.html +76 -0
- package/test/midi/index.js +183 -0
- package/test/midi/manifest.json +17 -0
- package/test/midi/service-worker.js +165 -0
- package/test/midi.dsp +8 -1
- package/test/organ1/create-node.js +177 -0
- package/test/organ1/dsp-meta.json +126 -0
- package/test/organ1/dsp-module.wasm +0 -0
- package/test/organ1/faust-ui/index.css +442 -0
- package/test/organ1/faust-ui/index.css.map +1 -0
- package/test/organ1/faust-ui/index.d.ts +1 -0
- package/test/organ1/faust-ui/index.js +3729 -0
- package/test/organ1/faust-ui/index.js.map +1 -0
- package/test/organ1/faustwasm/index.d.ts +1656 -0
- package/test/organ1/faustwasm/index.js +4783 -0
- package/test/organ1/faustwasm/index.js.map +7 -0
- package/test/organ1/icon.png +0 -0
- package/test/organ1/index.html +76 -0
- package/test/organ1/index.js +203 -0
- package/test/organ1/manifest.json +17 -0
- package/test/organ1/service-worker.js +181 -0
|
Binary file
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<link rel="manifest" href="./manifest.json">
|
|
5
|
+
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="UTF-8">
|
|
8
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
10
|
+
<title>Faust DSP</title>
|
|
11
|
+
<style>
|
|
12
|
+
body {
|
|
13
|
+
color: #b0b0b0;
|
|
14
|
+
background-color: #1f1f1f;
|
|
15
|
+
display: flex;
|
|
16
|
+
top: 0;
|
|
17
|
+
left: 0;
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
margin: 0;
|
|
21
|
+
position: absolute;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
main {
|
|
25
|
+
margin: 20px;
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
flex: 1 1 100%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
main span {
|
|
32
|
+
position: relative;
|
|
33
|
+
display: flex;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main span[hidden] {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main span span {
|
|
41
|
+
margin: 0px 10px;
|
|
42
|
+
flex: 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main span select {
|
|
46
|
+
flex: 2;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#dsp-switch {
|
|
50
|
+
margin: 20px auto;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#button-dsp {
|
|
54
|
+
padding: 10px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#div-faust-ui {
|
|
58
|
+
flex: 1 1 auto;
|
|
59
|
+
display: flex;
|
|
60
|
+
position: relative;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
color: black;
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
65
|
+
<link rel="stylesheet" href="./faust-ui/index.css">
|
|
66
|
+
</head>
|
|
67
|
+
|
|
68
|
+
<body>
|
|
69
|
+
<main>
|
|
70
|
+
<div id="div-faust-ui">
|
|
71
|
+
</div>
|
|
72
|
+
</main>
|
|
73
|
+
<script src="./index.js"></script>
|
|
74
|
+
</body>
|
|
75
|
+
|
|
76
|
+
</html>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Set to > 0 if the DSP is polyphonic
|
|
2
|
+
const FAUST_DSP_VOICES = 0;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import("./faustwasm").FaustAudioWorkletNode} FaustAudioWorkletNode
|
|
6
|
+
* @typedef {import("./faustwasm").FaustDspMeta} FaustDspMeta
|
|
7
|
+
* @typedef {import("./faustwasm").FaustUIDescriptor} FaustUIDescriptor
|
|
8
|
+
* @typedef {import("./faustwasm").FaustUIGroup} FaustUIGroup
|
|
9
|
+
* @typedef {import("./faustwasm").FaustUIItem} FaustUIItem
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Registers the service worker.
|
|
14
|
+
*/
|
|
15
|
+
if ("serviceWorker" in navigator) {
|
|
16
|
+
window.addEventListener("load", () => {
|
|
17
|
+
navigator.serviceWorker.register("./service-worker.js")
|
|
18
|
+
.then(reg => {
|
|
19
|
+
console.log("Service Worker registered", reg);
|
|
20
|
+
|
|
21
|
+
// Listen for updates to the existing service worker
|
|
22
|
+
reg.addEventListener("updatefound", () => {
|
|
23
|
+
const newWorker = reg.installing;
|
|
24
|
+
if (newWorker) {
|
|
25
|
+
console.log("New Service Worker found...");
|
|
26
|
+
newWorker.addEventListener("statechange", () => {
|
|
27
|
+
if (newWorker.state === "installed") {
|
|
28
|
+
// A new version has finished installing
|
|
29
|
+
if (navigator.serviceWorker.controller) {
|
|
30
|
+
// There's already a controlling SW, so this is an update
|
|
31
|
+
console.log("New version installed. Reloading...");
|
|
32
|
+
window.location.reload();
|
|
33
|
+
} else {
|
|
34
|
+
// First installation of the service worker
|
|
35
|
+
console.log("Service Worker installed for the first time.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
})
|
|
42
|
+
.catch(err => console.log("Service Worker registration failed", err));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @type {HTMLDivElement} */
|
|
47
|
+
const $divFaustUI = document.getElementById("div-faust-ui");
|
|
48
|
+
|
|
49
|
+
/** @type {typeof AudioContext} */
|
|
50
|
+
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
51
|
+
const audioContext = new AudioCtx({ latencyHint: 0.00001 });
|
|
52
|
+
audioContext.destination.channelInterpretation = "discrete";
|
|
53
|
+
audioContext.suspend();
|
|
54
|
+
|
|
55
|
+
// Declare faustNode as a global variable
|
|
56
|
+
let faustNode;
|
|
57
|
+
|
|
58
|
+
// Called at load time
|
|
59
|
+
(async () => {
|
|
60
|
+
|
|
61
|
+
// Import the create-node module
|
|
62
|
+
const { createFaustNode, createFaustUI } = await import("./create-node.js");
|
|
63
|
+
|
|
64
|
+
// To test the ScriptProcessorNode mode
|
|
65
|
+
// const result = await createFaustNode(audioContext, "osc", FAUST_DSP_VOICES, true, 512);
|
|
66
|
+
const result = await createFaustNode(audioContext, "osc", FAUST_DSP_VOICES);
|
|
67
|
+
faustNode = result.faustNode; // Assign to the global variable
|
|
68
|
+
if (!faustNode) throw new Error("Faust DSP not compiled");
|
|
69
|
+
|
|
70
|
+
// Create the Faust UI
|
|
71
|
+
await createFaustUI($divFaustUI, faustNode);
|
|
72
|
+
|
|
73
|
+
})();
|
|
74
|
+
|
|
75
|
+
// Synchronous function to resume AudioContext, to be called first in the synchronous event listener
|
|
76
|
+
function resumeAudioContext() {
|
|
77
|
+
if (audioContext.state === 'suspended') {
|
|
78
|
+
audioContext.resume().then(() => {
|
|
79
|
+
console.log('AudioContext resumed successfully');
|
|
80
|
+
}).catch(error => {
|
|
81
|
+
console.error('Error when resuming AudioContext:', error);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Function to start MIDI
|
|
87
|
+
function startMIDI() {
|
|
88
|
+
// Check if the browser supports the Web MIDI API
|
|
89
|
+
if (navigator.requestMIDIAccess) {
|
|
90
|
+
navigator.requestMIDIAccess().then(
|
|
91
|
+
midiAccess => {
|
|
92
|
+
console.log("MIDI Access obtained.");
|
|
93
|
+
for (let input of midiAccess.inputs.values()) {
|
|
94
|
+
input.onmidimessage = (event) => faustNode.midiMessage(event.data);
|
|
95
|
+
console.log(`Connected to input: ${input.name}`);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
() => console.error("Failed to access MIDI devices.")
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
console.log("Web MIDI API is not supported in this browser.");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Function to stop MIDI
|
|
106
|
+
function stopMIDI() {
|
|
107
|
+
// Check if the browser supports the Web MIDI API
|
|
108
|
+
if (navigator.requestMIDIAccess) {
|
|
109
|
+
navigator.requestMIDIAccess().then(
|
|
110
|
+
midiAccess => {
|
|
111
|
+
console.log("MIDI Access obtained.");
|
|
112
|
+
for (let input of midiAccess.inputs.values()) {
|
|
113
|
+
input.onmidimessage = null;
|
|
114
|
+
console.log(`Disconnected from input: ${input.name}`);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
() => console.error("Failed to access MIDI devices.")
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
console.log("Web MIDI API is not supported in this browser.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let sensorHandlersBound = false;
|
|
125
|
+
let midiHandlersBound = false;
|
|
126
|
+
|
|
127
|
+
// Function to activate MIDI and Sensors on user interaction
|
|
128
|
+
async function activateMIDISensors() {
|
|
129
|
+
|
|
130
|
+
// Import the create-node module
|
|
131
|
+
const { connectToAudioInput, requestPermissions } = await import("./create-node.js");
|
|
132
|
+
|
|
133
|
+
// Request permission for sensors
|
|
134
|
+
await requestPermissions();
|
|
135
|
+
|
|
136
|
+
// Activate sensor listeners
|
|
137
|
+
if (!sensorHandlersBound) {
|
|
138
|
+
await faustNode.startSensors();
|
|
139
|
+
sensorHandlersBound = true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Initialize the MIDI setup
|
|
143
|
+
if (!midiHandlersBound && FAUST_DSP_VOICES > 0) {
|
|
144
|
+
startMIDI();
|
|
145
|
+
midiHandlersBound = true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Connect the Faust node to the audio output
|
|
149
|
+
faustNode.connect(audioContext.destination);
|
|
150
|
+
|
|
151
|
+
// Connect the Faust node to the audio input
|
|
152
|
+
if (faustNode.numberOfInputs > 0) {
|
|
153
|
+
await connectToAudioInput(audioContext, null, faustNode, null);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Resume the AudioContext
|
|
157
|
+
if (audioContext.state === 'suspended') {
|
|
158
|
+
await audioContext.resume();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Function to suspend AudioContext, deactivate MIDI and Sensors on user interaction
|
|
163
|
+
async function deactivateAudioMIDISensors() {
|
|
164
|
+
|
|
165
|
+
// Suspend the AudioContext
|
|
166
|
+
if (audioContext.state === 'running') {
|
|
167
|
+
await audioContext.suspend();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Deactivate sensor listeners
|
|
171
|
+
if (sensorHandlersBound) {
|
|
172
|
+
faustNode.stopSensors();
|
|
173
|
+
sensorHandlersBound = false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Deactivate the MIDI setup
|
|
177
|
+
if (midiHandlersBound && FAUST_DSP_VOICES > 0) {
|
|
178
|
+
stopMIDI();
|
|
179
|
+
midiHandlersBound = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Event listener to handle user interaction
|
|
184
|
+
function handleUserInteraction() {
|
|
185
|
+
// Resume AudioContext synchronously
|
|
186
|
+
resumeAudioContext();
|
|
187
|
+
|
|
188
|
+
// Launch the activation of MIDI and Sensors
|
|
189
|
+
activateMIDISensors().catch(error => {
|
|
190
|
+
console.error('Error when activating audio, MIDI and sensors:', error);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Activate AudioContext, MIDI and Sensors on user interaction
|
|
195
|
+
window.addEventListener('click', handleUserInteraction);
|
|
196
|
+
window.addEventListener('touchstart', handleUserInteraction);
|
|
197
|
+
|
|
198
|
+
// Deactivate AudioContext, MIDI and Sensors on user interaction
|
|
199
|
+
window.addEventListener('visibilitychange', function () {
|
|
200
|
+
if (window.visibilityState === 'hidden') {
|
|
201
|
+
deactivateAudioMIDISensors();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Faust organ1",
|
|
3
|
+
"short_name": "organ1",
|
|
4
|
+
"start_url": "./index.html",
|
|
5
|
+
"description": "Progressive Web App for organ1",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"background_color": "#ffffff",
|
|
8
|
+
"theme_color": "#000000",
|
|
9
|
+
"orientation": "portrait",
|
|
10
|
+
"icons": [
|
|
11
|
+
{
|
|
12
|
+
"src": "./icon.png",
|
|
13
|
+
"sizes": "390x390",
|
|
14
|
+
"type": "image/png"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
|
|
3
|
+
// Set to > 0 if the DSP is polyphonic
|
|
4
|
+
const FAUST_DSP_VOICES = 0;
|
|
5
|
+
// Set to true if the DSP has an effect
|
|
6
|
+
const FAUST_DSP_HAS_EFFECT = false;
|
|
7
|
+
|
|
8
|
+
const CACHE_NAME = "organ1_20250201-1155"; // Cache name with versioning
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List of essential resources required for the **Mono DSP** version of the application.
|
|
12
|
+
*
|
|
13
|
+
* - These files are cached to enable offline functionality and improve loading speed.
|
|
14
|
+
* - Includes the main HTML, JavaScript, and CSS files required for the app.
|
|
15
|
+
* - Contains Faust-related files needed for DSP processing.
|
|
16
|
+
*/
|
|
17
|
+
const MONO_RESOURCES = [
|
|
18
|
+
"./index.html",
|
|
19
|
+
"./index.js",
|
|
20
|
+
"./create-node.js",
|
|
21
|
+
"./faust-ui/index.js",
|
|
22
|
+
"./faust-ui/index.css",
|
|
23
|
+
"./faustwasm/index.js",
|
|
24
|
+
"./dsp-module.wasm",
|
|
25
|
+
"./dsp-meta.json"
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* List of resources for the **Polyphonic DSP** version of the application.
|
|
30
|
+
*
|
|
31
|
+
* - Extends the mono resource list by adding a **mixer module**.
|
|
32
|
+
* - The mixer module is required to handle multiple simultaneous voices used in a polyphonic instrument.
|
|
33
|
+
*/
|
|
34
|
+
const POLY_RESOURCES = [
|
|
35
|
+
...MONO_RESOURCES,
|
|
36
|
+
"./mixer-module.wasm",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* List of resources for the **Polyphonic DSP with Effects** version.
|
|
41
|
+
*
|
|
42
|
+
* - Extends the polyphonic resource list by adding an **effect module**.
|
|
43
|
+
* - The effect module allows applying audio effects to the polyphonic instrument.
|
|
44
|
+
*/
|
|
45
|
+
const POLY_EFFECT_RESOURCES = [
|
|
46
|
+
...POLY_RESOURCES,
|
|
47
|
+
"./effect-module.wasm",
|
|
48
|
+
"./effect-meta.json",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/** @type {ServiceWorkerGlobalScope} */
|
|
52
|
+
const serviceWorkerGlobalScope = self;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Install the service worker, cache essential resources, and prepare for immediate activation.
|
|
56
|
+
*
|
|
57
|
+
* - Opens the cache and stores required assets based on the app's configuration.
|
|
58
|
+
* - Ensures resources are preloaded for offline access.
|
|
59
|
+
*/
|
|
60
|
+
serviceWorkerGlobalScope.addEventListener("install", (event) => {
|
|
61
|
+
console.log("Service worker installed");
|
|
62
|
+
|
|
63
|
+
// Force activation of the new service worker without waiting
|
|
64
|
+
serviceWorkerGlobalScope.skipWaiting();
|
|
65
|
+
|
|
66
|
+
event.waitUntil((async () => {
|
|
67
|
+
const cache = await caches.open(CACHE_NAME);
|
|
68
|
+
const resources = (FAUST_DSP_VOICES && FAUST_DSP_HAS_EFFECT)
|
|
69
|
+
? POLY_EFFECT_RESOURCES
|
|
70
|
+
: (FAUST_DSP_VOICES ? POLY_RESOURCES : MONO_RESOURCES);
|
|
71
|
+
try {
|
|
72
|
+
return cache.addAll(resources);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("Failed to cache resources during install:", error);
|
|
75
|
+
}
|
|
76
|
+
})());
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Handles the activation of the Service Worker.
|
|
81
|
+
*
|
|
82
|
+
* - Claims control over all clients immediately, bypassing the default behavior that
|
|
83
|
+
* requires a page reload before the new service worker takes effect.
|
|
84
|
+
* - Removes old caches that do not match the current version.
|
|
85
|
+
* - Once claimed, (optionally) reloads all active window clients to ensure they are
|
|
86
|
+
* controlled by the latest version of the service worker.
|
|
87
|
+
* - This approach ensures that updates to the service worker take effect immediately.
|
|
88
|
+
*/
|
|
89
|
+
serviceWorkerGlobalScope.addEventListener("activate", (event) => {
|
|
90
|
+
console.log("Service worker activated");
|
|
91
|
+
event.waitUntil(
|
|
92
|
+
(async () => {
|
|
93
|
+
// Clean up old caches that don't match the current CACHE_NAME
|
|
94
|
+
const cacheNames = await caches.keys();
|
|
95
|
+
await Promise.all(
|
|
96
|
+
cacheNames.map((cacheName) => {
|
|
97
|
+
if (cacheName !== CACHE_NAME && cacheName.startsWith("organ1_")) {
|
|
98
|
+
console.log("Deleting old cache:", cacheName);
|
|
99
|
+
return caches.delete(cacheName);
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Take control of all active clients
|
|
105
|
+
await clients.claim();
|
|
106
|
+
|
|
107
|
+
// Optionally reload all open windows to ensure they're using the new SW
|
|
108
|
+
const windowClients = await clients.matchAll({ type: "window" });
|
|
109
|
+
windowClients.forEach((client) => {
|
|
110
|
+
client.navigate(client.url);
|
|
111
|
+
});
|
|
112
|
+
})()
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Adjusts response headers to enforce Cross-Origin Opener Policy (COOP) and Cross-Origin Embedder Policy (COEP).
|
|
118
|
+
*
|
|
119
|
+
* - Ensures that the response is served with `Cross-Origin-Opener-Policy: same-origin`
|
|
120
|
+
* and `Cross-Origin-Embedder-Policy: require-corp`.
|
|
121
|
+
* - Required for enabling **cross-origin isolated** environments in web applications.
|
|
122
|
+
* - Necessary for features like **SharedArrayBuffer**, WebAssembly threads, and
|
|
123
|
+
* high-performance APIs that require isolation.
|
|
124
|
+
* - Creates a new `Response` object with the modified headers while preserving
|
|
125
|
+
* the original response body and status.
|
|
126
|
+
*
|
|
127
|
+
* @param {Response} response - The original HTTP response object.
|
|
128
|
+
* @returns {Response} A new response with updated security headers.
|
|
129
|
+
*/
|
|
130
|
+
const getCrossOriginIsolatedResponse = (response) => {
|
|
131
|
+
// Modify headers to include COOP & COEP
|
|
132
|
+
const headers = new Headers(response.headers);
|
|
133
|
+
headers.set("Cross-Origin-Opener-Policy", "same-origin");
|
|
134
|
+
headers.set("Cross-Origin-Embedder-Policy", "require-corp");
|
|
135
|
+
|
|
136
|
+
// Create a new response with the modified headers
|
|
137
|
+
return new Response(response.body, {
|
|
138
|
+
status: response.status,
|
|
139
|
+
statusText: response.statusText,
|
|
140
|
+
headers
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Intercepts fetch requests and enforces COOP and COEP headers for security.
|
|
146
|
+
*
|
|
147
|
+
* - Checks if the requested resource is available in the cache:
|
|
148
|
+
* - If found, returns a cached response with `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` headers.
|
|
149
|
+
* - If not found, fetches the resource from the network, applies COOP/COEP headers, and caches the response (for GET requests).
|
|
150
|
+
* - Ensures cross-origin isolation, required for APIs like `SharedArrayBuffer` and WebAssembly threading.
|
|
151
|
+
* - Handles network errors gracefully by returning a 503 "Service Unavailable" response when needed.
|
|
152
|
+
*
|
|
153
|
+
* @param {FetchEvent} event - The fetch event triggered by the browser.
|
|
154
|
+
*/
|
|
155
|
+
serviceWorkerGlobalScope.addEventListener("fetch", (event) => {
|
|
156
|
+
event.respondWith((async () => {
|
|
157
|
+
const cache = await caches.open(CACHE_NAME);
|
|
158
|
+
const cachedResponse = await cache.match(event.request);
|
|
159
|
+
|
|
160
|
+
if (cachedResponse) {
|
|
161
|
+
return getCrossOriginIsolatedResponse(cachedResponse);
|
|
162
|
+
} else {
|
|
163
|
+
try {
|
|
164
|
+
const fetchResponse = await fetch(event.request);
|
|
165
|
+
|
|
166
|
+
if (event.request.method === "GET" && fetchResponse && fetchResponse.status === 200 && fetchResponse.type === "basic") {
|
|
167
|
+
const modifiedResponse = getCrossOriginIsolatedResponse(fetchResponse);
|
|
168
|
+
// Store the modified response in the cache
|
|
169
|
+
await cache.put(event.request, modifiedResponse.clone());
|
|
170
|
+
// Return the modified response to the browser
|
|
171
|
+
return modifiedResponse;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return fetchResponse;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("Network access error", error);
|
|
177
|
+
return new Response("Network error", { status: 503, statusText: "Service Unavailable" });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
})());
|
|
181
|
+
});
|