node-web-audio-api 0.21.4 → 0.21.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.21.5 (23/12/2024)
4
+
5
+ - Fix: Use module import for `AudioWorklet#addModule`
6
+ - Feat: Resolve `AudioWorkletNode` when installed in `node_modules`
7
+ - Ensure support of `AudioWorkletNode` that use Web Assembly
8
+
3
9
  ## v0.21.4 (16/12/2024)
4
10
 
5
11
  - Update upstream crate to [v1.1.0](https://github.com/orottier/web-audio-api-rs/blob/main/CHANGELOG.md#version-110-2024-12-11)
@@ -28,40 +28,38 @@ const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch
28
28
  /**
29
29
  * Retrieve code with different module resolution strategies
30
30
  * - file - absolute or relative to cwd path
31
- * - URL
32
- * - Blob
31
+ *
32
+ * - URL - do not support import within module
33
+ * - Blob - do not support import within module
33
34
  * - fallback: relative to caller site
34
- * + in fs
35
+ * + in fs - support import within module
35
36
  * + caller site is url - required for wpt, probably no other use case
36
37
  */
37
38
  const resolveModule = async (moduleUrl) => {
38
- let code;
39
+ let code = null;
40
+ let absPathname = null;
39
41
 
40
42
  if (existsSync(moduleUrl)) {
41
- const pathname = moduleUrl;
42
-
43
- try {
44
- const buffer = await fs.readFile(pathname);
45
- code = buffer.toString();
46
- } catch (err) {
47
- throw new Error(`Failed to execute 'addModule' on 'AudioWorklet': ${err.message}`);
43
+ if (path.isAbsolute(moduleUrl)) {
44
+ absPathname = moduleUrl;
45
+ } else { // moduleUrl is relative to process.cwd();
46
+ absPathname = path.join(process.cwd(), moduleUrl);
48
47
  }
49
48
  } else if (moduleUrl.startsWith('http')) {
50
49
  try {
51
50
  const res = await fetch(moduleUrl);
52
51
  code = await res.text();
53
52
  } catch (err) {
54
- throw new Error(`Failed to execute 'addModule' on 'AudioWorklet': ${err.message}`);
53
+ throw new DOMException(`Failed to execute 'addModule' on 'AudioWorklet': ${err.message}`, 'AbortError');
55
54
  }
56
55
  } else if (moduleUrl.startsWith('blob:')) {
57
56
  try {
58
57
  const blob = resolveObjectURL(moduleUrl);
59
58
  code = await blob.text();
60
59
  } catch (err) {
61
- throw new Error(`Failed to execute 'addModule' on 'AudioWorklet': ${err.message}`);
60
+ throw new DOMException(`Failed to execute 'addModule' on 'AudioWorklet': ${err.message}`, 'AbortError');
62
61
  }
63
62
  } else {
64
- // get caller site from error stack trace
65
63
  const callerSite = caller(2);
66
64
 
67
65
  if (callerSite.startsWith('http')) { // this branch exists for wpt where caller site is an url
@@ -80,27 +78,28 @@ const resolveModule = async (moduleUrl) => {
80
78
  const res = await fetch(url);
81
79
  code = await res.text();
82
80
  } catch (err) {
83
- throw new Error(`Failed to execute 'addModule' on 'AudioWorklet': ${err.message}`);
81
+ throw new DOMException(`Failed to execute 'addModule' on 'AudioWorklet': ${err.message}`, 'AbortError');
84
82
  }
85
83
  } else {
86
- const dirname = callerSite.substr(0, callerSite.lastIndexOf(path.sep));
84
+ // filesystem, relative to caller site or in node_modules
85
+ const dirname = callerSite.substring(0, callerSite.lastIndexOf(path.sep));
87
86
  const absDirname = dirname.replace('file://', '');
88
87
  const pathname = path.join(absDirname, moduleUrl);
89
88
 
90
- if (existsSync(pathname)) {
89
+ if (existsSync(pathname)) { // relative to caller site
90
+ absPathname = pathname;
91
+ } else {
91
92
  try {
92
- const buffer = await fs.readFile(pathname);
93
- code = buffer.toString();
93
+ // try resolve according to process.cwd()
94
+ absPathname = require.resolve(moduleUrl, { paths: [process.cwd()] });
94
95
  } catch (err) {
95
- throw new Error(`Failed to execute 'addModule' on 'AudioWorklet': ${err.message}`);
96
+ throw new DOMException(`Failed to execute 'addModule' on 'AudioWorklet': Cannot resolve module ${moduleUrl}`, 'AbortError');
96
97
  }
97
- } else {
98
- throw new Error(`Failed to execute 'addModule' on 'AudioWorklet': Cannot resolve module ${moduleUrl}`);
99
98
  }
100
99
  }
101
100
  }
102
101
 
103
- return code;
102
+ return { absPathname, code };
104
103
  }
105
104
 
106
105
  class AudioWorklet {
@@ -125,6 +124,9 @@ class AudioWorklet {
125
124
  }
126
125
 
127
126
  #bindEvents() {
127
+ // @todo
128
+ // - better error handling, stack trace, etc.
129
+ // - handle 'node-web-audio-api:worklet:ctor-error' message
128
130
  this.#port.on('message', event => {
129
131
  switch (event.cmd) {
130
132
  case 'node-web-audio-api:worklet:module-added': {
@@ -135,10 +137,9 @@ class AudioWorklet {
135
137
  break;
136
138
  }
137
139
  case 'node-web-audio-api:worklet:add-module-failed': {
138
- const { promiseId, ctor, name, message } = event;
140
+ const { promiseId, err } = event;
139
141
  const { reject } = this.#idPromiseMap.get(promiseId);
140
142
  this.#idPromiseMap.delete(promiseId);
141
- const err = new globalThis[ctor](message, name);
142
143
  reject(err);
143
144
  break;
144
145
  }
@@ -161,7 +162,9 @@ class AudioWorklet {
161
162
  }
162
163
 
163
164
  async addModule(moduleUrl) {
164
- const code = await resolveModule(moduleUrl);
165
+ // @important - `resolveModule` must be called first because it uses `caller`
166
+ // which will return `null` if this is not in the first line...
167
+ const resolved = await resolveModule(moduleUrl);
165
168
 
166
169
  // launch Worker if not exists
167
170
  if (!this.#port) {
@@ -187,7 +190,8 @@ class AudioWorklet {
187
190
 
188
191
  this.#port.postMessage({
189
192
  cmd: 'node-web-audio-api:worklet:add-module',
190
- code,
193
+ moduleUrl: resolved.absPathname,
194
+ code: resolved.code,
191
195
  promiseId,
192
196
  });
193
197
  });
@@ -298,7 +298,7 @@ globalThis.registerProcessor = function registerProcessor(name, processorCtor) {
298
298
  // process.stdout.write('closing worklet');
299
299
  // });
300
300
 
301
- parentPort.on('message', event => {
301
+ parentPort.on('message', async event => {
302
302
  switch (event.cmd) {
303
303
  case 'node-web-audio-api:worklet:init': {
304
304
  const { workletId, processors, promiseId } = event;
@@ -313,11 +313,18 @@ parentPort.on('message', event => {
313
313
  break;
314
314
  }
315
315
  case 'node-web-audio-api:worklet:add-module': {
316
- const { code, promiseId } = event;
317
- const func = new Function('AudioWorkletProcessor', 'registerProcessor', code);
316
+ const { moduleUrl, code, promiseId } = event;
318
317
 
319
318
  try {
320
- func(AudioWorkletProcessor, registerProcessor);
319
+ // 1. If given module is a "real" file, we can import it as is,
320
+ // 2. If module is a blob or loaded from an URL, we use the raw text as
321
+ // input. In this case, if the module uses `import` it will crash
322
+ if (moduleUrl !== null) {
323
+ await import(moduleUrl);
324
+ } else {
325
+ await import(`data:text/javascript;base64,${btoa(unescape(encodeURIComponent(code)))}`);
326
+ }
327
+
321
328
  // send registered param descriptors on main thread and resolve Promise
322
329
  parentPort.postMessage({
323
330
  cmd: 'node-web-audio-api:worklet:module-added',
@@ -327,9 +334,7 @@ parentPort.on('message', event => {
327
334
  parentPort.postMessage({
328
335
  cmd: 'node-web-audio-api:worklet:add-module-failed',
329
336
  promiseId,
330
- ctor: err.constructor.name,
331
- name: err.name,
332
- message: err.message,
337
+ err,
333
338
  });
334
339
  }
335
340
  break;
@@ -338,7 +343,7 @@ parentPort.on('message', event => {
338
343
  const { name, id, options, port } = event;
339
344
  const ctor = nameProcessorCtorMap.get(name);
340
345
 
341
- // rewrap options of interest for the AudioWorkletNodeBaseClass
346
+ // re-wrap options of interest for the AudioWorkletNodeBaseClass
342
347
  pendingProcessorConstructionData = {
343
348
  port,
344
349
  numberOfInputs: options.numberOfInputs,
@@ -352,6 +357,7 @@ parentPort.on('message', event => {
352
357
  instance = new ctor(options);
353
358
  } catch (err) {
354
359
  port.postMessage({ cmd: 'node-web-audio-api:worklet:ctor-error', err });
360
+ return;
355
361
  }
356
362
 
357
363
  pendingProcessorConstructionData = null;
@@ -222,7 +222,8 @@ module.exports = (jsExport, nativeBinding) => {
222
222
  );
223
223
 
224
224
  this.#port.on('message', msg => {
225
- // ErrorEvent named processorerror
225
+ // Handle 'processorerror' ErrorEvent
226
+ // cf. https://webaudio.github.io/web-audio-api/#dom-audioworkletnode-onprocessorerror
226
227
  switch (msg.cmd) {
227
228
  case 'node-web-audio-api:worklet:ctor-error': {
228
229
  const message = `Failed to construct '${parsedName}' AudioWorkletProcessor: ${msg.err.message}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-web-audio-api",
3
- "version": "0.21.4",
3
+ "version": "0.21.5",
4
4
  "author": "Benjamin Matuszewski",
5
5
  "description": "Node.js bindings for web-audio-api-rs using napi-rs",
6
6
  "exports": {