diff --git a/.gitignore b/.gitignore index 775cc5a..c133c31 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ prebuilds/airtunes2-v2.3.0-electron-v106-win32-x64.tar.gz mirrors.raw .yarn/ prebuilds/ +dist/ diff --git a/lib/device_airtunes.js b/lib/device_airtunes.js index ce2cad1..5370123 100644 --- a/lib/device_airtunes.js +++ b/lib/device_airtunes.js @@ -1,15 +1,15 @@ -var dgram = require('dgram'), - events = require('events'), - util = require('util'), - config = require('./config.js'), - nu = require('./num_util.js'), - RTSP = require('./rtsp.js'); - var bindings = null; -const crypto = require('crypto'); +var dgram = require("dgram"), + events = require("events"), + util = require("util"), + config = require("./config.js"), + nu = require("./num_util.js"), + RTSP = require("./rtsp.js"); +var bindings = null; +const crypto = require("crypto"); var RTP_HEADER_SIZE = 12; -const fs = require('fs'); -const process = require('process'); -const UDPServers = require('./udp_servers.js'); +const fs = require("fs"); +const process = require("process"); +const UDPServers = require("./udp_servers.js"); function BufferWithNames(size) { // Very simple solution for this immediate need... @@ -37,117 +37,163 @@ BufferWithNames.prototype.getLatestNamed = function (name) { function AirTunesDevice(host, audioOut, options, mode = 0, txt = "") { events.EventEmitter.call(this); - if(!host) - throw new Error('host is mandatory'); + if (!host) throw new Error("host is mandatory"); - this.bindings = null - this.bindingsok = false + this.bindings = null; + this.bindingsok = false; this.audioPacketHistory = new BufferWithNames(100); - try{ - if (!(process.platform == 'darwin')) { // MacOSX arm64 segfaults on airtunes.node - this.bindings = require('../build/Release/airtunes'); - this.bindingsok = true} + try { + if (!(process.platform == "darwin")) { + // MacOSX arm64 segfaults on airtunes.node + this.bindings = require("../build/Release/airtunes.node"); + this.bindingsok = true; + } } catch (e) {} this.udpServers = new UDPServers(); this.audioOut = audioOut; - this.type = 'airtunes'; - this.options = options + this.type = "airtunes"; + this.options = options; this.host = host; this.port = options?.port || 5000; - this.key = this.host + ':' + this.port; + this.key = this.host + ":" + this.port; this.mode = mode; // Homepods with or without passcode // if(options.password != null && legacy == true){ - // this.mode = 1; // Airport / Shairport legacy passcode mode - // this.mode = 2 // MFi mode + // this.mode = 1; // Airport / Shairport legacy passcode mode + // this.mode = 2 // MFi mode // } - this.forceAlac = options.forceAlac ?? true + this.forceAlac = options.forceAlac ?? true; // this.skipAutoVolume = options.skipAutoVolume ?? false this.statusflags = []; - this.alacEncoding = options?.alacEncoding ?? true - this.airplay2 = options?.airplay2 ?? false + this.alacEncoding = options?.alacEncoding ?? true; + this.airplay2 = options?.airplay2 ?? false; this.txt = txt; this.borkedshp = false; - console.log("yasc",this.txt) - console.log('port', this.port) - let a = this.txt.filter((u) => String(u).startsWith('et=')) - if((a[0]?? "").includes('4')){ + console.log("yasc", this.txt); + console.log("port", this.port); + let a = this.txt.filter((u) => String(u).startsWith("et=")); + if ((a[0] ?? "").includes("4")) { this.mode = 2; } - let b = this.txt.filter((u) => String(u).startsWith('cn=')) - if (!this.forceAlac){ - if((b[0]?? "").includes('0')){ - this.alacEncoding = false; - }} - let c = this.txt.filter((u) => String(u).startsWith('sf=')) - this.statusflags = c[0] ? parseInt(c[0].substring(3)).toString(2).split('') : [] + let b = this.txt.filter((u) => String(u).startsWith("cn=")); + if (!this.forceAlac) { + if ((b[0] ?? "").includes("0")) { + this.alacEncoding = false; + } + } + let c = this.txt.filter((u) => String(u).startsWith("sf=")); + this.statusflags = c[0] + ? parseInt(c[0].substring(3)).toString(2).split("") + : []; if (c.length == 0) { - c = this.txt.filter((u) => String(u).startsWith('flags=')) - this.statusflags = c[0] ? parseInt(c[0].substring(6)).toString(2).split('') : [] + c = this.txt.filter((u) => String(u).startsWith("flags=")); + this.statusflags = c[0] + ? parseInt(c[0].substring(6)).toString(2).split("") + : []; } this.needPassword = false; this.needPin = false; this.transient = false; - let d = "" - d = this.txt.filter((u) => String(u).startsWith('features=')) - if(d.length == 0) d = this.txt.filter((u) => String(u).startsWith('ft=')) - let features_set = d.length > 0 ? d[0].substring(d[0].indexOf("=")+1).split(',') : [] - this.features = [... features_set.length > 0 ? parseInt(features_set[0]).toString(2).split('') : [], ... features_set.length > 1 ? parseInt(features_set[1]).toString(2).split('') : []] - if (this.features.length > 0){ - this.transient = (this.features[this.features.length - 1 - 48] == '1') + let d = ""; + d = this.txt.filter((u) => String(u).startsWith("features=")); + if (d.length == 0) d = this.txt.filter((u) => String(u).startsWith("ft=")); + let features_set = + d.length > 0 ? d[0].substring(d[0].indexOf("=") + 1).split(",") : []; + this.features = [ + ...(features_set.length > 0 + ? parseInt(features_set[0]).toString(2).split("") + : []), + ...(features_set.length > 1 + ? parseInt(features_set[1]).toString(2).split("") + : []), + ]; + if (this.features.length > 0) { + this.transient = this.features[this.features.length - 1 - 48] == "1"; } - if (this.statusflags != []){ - let PasswordRequired = (this.statusflags[this.statusflags.length - 1 - 7] == '1') - let PinRequired = (this.statusflags[this.statusflags.length - 1 - 3] == '1') - let OneTimePairingRequired = (this.statusflags[this.statusflags.length - 1 - 9] == '1') - console.log('needPss', PasswordRequired, PinRequired, OneTimePairingRequired) + if (this.statusflags != []) { + let PasswordRequired = + this.statusflags[this.statusflags.length - 1 - 7] == "1"; + let PinRequired = this.statusflags[this.statusflags.length - 1 - 3] == "1"; + let OneTimePairingRequired = + this.statusflags[this.statusflags.length - 1 - 9] == "1"; + console.log( + "needPss", + PasswordRequired, + PinRequired, + OneTimePairingRequired + ); this.needPassword = PasswordRequired; - this.needPin = (PinRequired || OneTimePairingRequired) - this.transient = (!(PasswordRequired || PinRequired || OneTimePairingRequired)) ?? true - console.log('needPss', this.needPassword) + this.needPin = PinRequired || OneTimePairingRequired; + this.transient = + !(PasswordRequired || PinRequired || OneTimePairingRequired) ?? true; + console.log("needPss", this.needPassword); } - console.log('transient', this.transient) + console.log("transient", this.transient); // detect old shairports with broken text - let oldver1 = this.txt.filter((u) => String(u).startsWith('sm=')) - let oldver2 = this.txt.filter((u) => String(u).startsWith('sv=')) - if ((b[0]?? "") == 'cn=0,1' && (a[0]?? "") == 'et=0,1' && (oldver1[0]?? "") == 'sm=false' && (oldver2[0]?? "") == 'sv=false' && this.statusflags.length == 0){ - console.log('borked shairport found') - this.alacEncoding = true + let oldver1 = this.txt.filter((u) => String(u).startsWith("sm=")); + let oldver2 = this.txt.filter((u) => String(u).startsWith("sv=")); + if ( + (b[0] ?? "") == "cn=0,1" && + (a[0] ?? "") == "et=0,1" && + (oldver1[0] ?? "") == "sm=false" && + (oldver2[0] ?? "") == "sv=false" && + this.statusflags.length == 0 + ) { + console.log("borked shairport found"); + this.alacEncoding = true; this.borkedshp = true; } - let k = this.txt.filter((u) => String(u).startsWith('am=')) - if ((k[0] ?? "").includes("AppleTV3,1") || (k[0] ?? "").includes("AirReceiver3,1") || (k[0] ?? "").includes("AirRecever3,1") || (k[0] ?? "").includes('Shairport')){ - this.alacEncoding = true - this.airplay2 = false + let k = this.txt.filter((u) => String(u).startsWith("am=")); + if ( + (k[0] ?? "").includes("AppleTV3,1") || + (k[0] ?? "").includes("AirReceiver3,1") || + (k[0] ?? "").includes("AirRecever3,1") || + (k[0] ?? "").includes("Shairport") + ) { + this.alacEncoding = true; + this.airplay2 = false; } - k = this.txt.filter((u) => String(u).startsWith('rmodel=')) - if ((k[0] ?? "").includes("AppleTV3,1") || (k[0] ?? "").includes("AirReceiver3,1") || (k[0] ?? "").includes("AirRecever3,1") || (k[0] ?? "").includes('Shairport')){ - this.alacEncoding = true - this.airplay2 = false + k = this.txt.filter((u) => String(u).startsWith("rmodel=")); + if ( + (k[0] ?? "").includes("AppleTV3,1") || + (k[0] ?? "").includes("AirReceiver3,1") || + (k[0] ?? "").includes("AirRecever3,1") || + (k[0] ?? "").includes("Shairport") + ) { + this.alacEncoding = true; + this.airplay2 = false; } - let manufacturer = this.txt.filter((u) => String(u).startsWith('manufacturer=')) - if ((manufacturer[0] ?? "").includes('Sonos')){ + let manufacturer = this.txt.filter((u) => + String(u).startsWith("manufacturer=") + ); + if ((manufacturer[0] ?? "").includes("Sonos")) { this.mode = 2; - this.needPin = true + this.needPin = true; + } + console.log("needPin", this.needPin); + console.log("mode-atv", this.mode); + console.log("alacEncoding", this.alacEncoding); + try { + this.rtsp = new RTSP.Client( + options.volume || 50, + options.password || null, + audioOut, + { + mode: this.mode, + txt: this.txt, + alacEncoding: this.alacEncoding, + needPassword: this.needPassword, + airplay2: this.airplay2, + needPin: this.needPin, + debug: options.debug, + transient: this.transient, + borkedshp: this.borkedshp, + } + ); + } catch (e) { + console.log("rtsp error", e); } - console.log("needPin",this.needPin) - console.log("mode-atv",this.mode) - console.log("alacEncoding",this.alacEncoding) - try{ - this.rtsp = new RTSP.Client(options.volume || 50, options.password || null, audioOut, - { - mode: this.mode, - txt: this.txt, - alacEncoding: this.alacEncoding, - needPassword: this.needPassword, - airplay2: this.airplay2, - needPin: this.needPin, - debug: options.debug, - transient: this.transient, - borkedshp: this.borkedshp, - });} catch(e){ - console.log("rtsp error",e)} this.audioCallback = null; this.encoder = []; this.credentials = null; @@ -170,18 +216,18 @@ function AirTunesDevice(host, audioOut, options, mode = 0, txt = "") { util.inherits(AirTunesDevice, events.EventEmitter); -AirTunesDevice.prototype.start = function() { +AirTunesDevice.prototype.start = function () { var self = this; - this.audioSocket = dgram.createSocket('udp4'); + this.audioSocket = dgram.createSocket("udp4"); // Wait until timing and control ports are chosen. We need them in RTSP handshake. - this.udpServers.on('ports', function(err) { - if(err) { + this.udpServers.on("ports", function (err) { + if (err) { console.log(err.code); - self.status = 'stopped'; - self.emit('status', 'stopped'); - console.log('port issues'); - self.emit('error', 'udp_ports', err.code); + self.status = "stopped"; + self.emit("status", "stopped"); + console.log("port issues"); + self.emit("error", "udp_ports", err.code); return; } @@ -191,130 +237,145 @@ AirTunesDevice.prototype.start = function() { this.udpServers.bind(this.host); }; -AirTunesDevice.prototype.doHandshake = function() { +AirTunesDevice.prototype.doHandshake = function () { var self = this; - try{ - if (this.rtsp == null){ - try{ - this.rtsp = new RTSP.Client(this.options.volume || 30, this.options.password || null, this.audioOut, - { - mode: this.mode, - txt: this.txt, - alacEncoding: this.alacEncoding, - needPassword: this.needPassword, - airplay2: this.airplay2, - needPin: this.needPin, - debug: true, - transient: this.transient, - borkedshp: this.borkedshp, - });} catch(e){ - console.log(e)} - } - this.rtsp.on('config', function(setup) { - self.audioLatency = setup.audioLatency; - self.requireEncryption = setup.requireEncryption; - self.serverPort = setup.server_port; - self.controlPort = setup.control_port; - self.timingPort = setup.timing_port; - self.credentials = setup.credentials ; - }); + try { + if (this.rtsp == null) { + try { + this.rtsp = new RTSP.Client( + this.options.volume || 30, + this.options.password || null, + this.audioOut, + { + mode: this.mode, + txt: this.txt, + alacEncoding: this.alacEncoding, + needPassword: this.needPassword, + airplay2: this.airplay2, + needPin: this.needPin, + debug: true, + transient: this.transient, + borkedshp: this.borkedshp, + } + ); + } catch (e) { + console.log(e); + } + } + this.rtsp.on("config", function (setup) { + self.audioLatency = setup.audioLatency; + self.requireEncryption = setup.requireEncryption; + self.serverPort = setup.server_port; + self.controlPort = setup.control_port; + self.timingPort = setup.timing_port; + self.credentials = setup.credentials; + }); - this.rtsp.on('ready', function() { - self.relayAudio(); - }); + this.rtsp.on("ready", function () { + self.relayAudio(); + }); - this.rtsp.on('need_password', function() { - self.emit('status','need_password'); - }); + this.rtsp.on("need_password", function () { + self.emit("status", "need_password"); + }); - this.rtsp.on('pair_failed', function() { - self.emit('status','pair_failed'); - }); + this.rtsp.on("pair_failed", function () { + self.emit("status", "pair_failed"); + }); - this.rtsp.on('pair_success', function() { - self.emit('status','pair_success'); - }); + this.rtsp.on("pair_success", function () { + self.emit("status", "pair_success"); + }); - this.rtsp.on('end', function(err) { - console.log(err); - self.cleanup(); + this.rtsp.on("end", function (err) { + console.log(err); + self.cleanup(); - if(err !== 'stopped') - self.emit(err); - }); - } catch(e){ - console.log(e) + if (err !== "stopped") self.emit(err); + }); + } catch (e) { + console.log(e); } // console.log(this.udpServers, this.host,this.port) this.rtsp.startHandshake(this.udpServers, this.host, this.port); }; -AirTunesDevice.prototype.relayAudio = function() { +AirTunesDevice.prototype.relayAudio = function () { var self = this; - this.status = 'ready'; - this.emit('status', 'ready'); - - - this.audioCallback = function(packet) { - var airTunes = makeAirTunesPacket(packet, self.encoder, self.requireEncryption, self.alacEncoding, self.credentials, self.bindings, self.bindingsok); + this.status = "ready"; + this.emit("status", "ready"); + + this.audioCallback = function (packet) { + var airTunes = makeAirTunesPacket( + packet, + self.encoder, + self.requireEncryption, + self.alacEncoding, + self.credentials, + self.bindings, + self.bindingsok + ); // if (self.credentials) { // airTunes = self.credentials.encrypt(airTunes) // } - if(self.audioSocket == null){ - self.audioSocket = dgram.createSocket('udp4'); + if (self.audioSocket == null) { + self.audioSocket = dgram.createSocket("udp4"); } self.audioSocket.send( - airTunes, 0, airTunes.length, - self.serverPort, self.host + airTunes, + 0, + airTunes.length, + self.serverPort, + self.host ); }; -// this.sendAirTunesPacket = function(airTunes) { -// try{ -// if(self.audioSocket == null){ -// self.audioSocket = dgram.createSocket('udp4'); -// } -// self.audioSocket.send( -// airTunes, 0, airTunes.length, -// self.serverPort, self.host -// );} catch(e){ - -// console.log('send error',e) -// } -// }; - -// this.audioCallback = function(packet) { -// var airTunes = makeAirTunesPacket(packet, self.encoder, self.requireEncryption, self.alacEncoding, self.credentials, self.bindings, self.bindingsok); -// try{ -// self.sendAirTunesPacket(airTunes); -// self.audioPacketHistory.add(packet.seq, airTunes); // If we need to resend it -// } catch(e){} -// }; - -// this.udpServers.on('resendRequested', function (missedSeq, count) { -// try{ -// for (var i = 0; i < count; i++) { -// airTunes = self.audioPacketHistory.getLatestNamed(missedSeq + i); -// if (airTunes != null) -// self.sendAirTunesPacket(airTunes);}} -// catch (_){} -// }); - - this.audioOut.on('packet', this.audioCallback); + // this.sendAirTunesPacket = function(airTunes) { + // try{ + // if(self.audioSocket == null){ + // self.audioSocket = dgram.createSocket('udp4'); + // } + // self.audioSocket.send( + // airTunes, 0, airTunes.length, + // self.serverPort, self.host + // );} catch(e){ + + // console.log('send error',e) + // } + // }; + + // this.audioCallback = function(packet) { + // var airTunes = makeAirTunesPacket(packet, self.encoder, self.requireEncryption, self.alacEncoding, self.credentials, self.bindings, self.bindingsok); + // try{ + // self.sendAirTunesPacket(airTunes); + // self.audioPacketHistory.add(packet.seq, airTunes); // If we need to resend it + // } catch(e){} + // }; + + // this.udpServers.on('resendRequested', function (missedSeq, count) { + // try{ + // for (var i = 0; i < count; i++) { + // airTunes = self.audioPacketHistory.getLatestNamed(missedSeq + i); + // if (airTunes != null) + // self.sendAirTunesPacket(airTunes);}} + // catch (_){} + // }); + + this.audioOut.on("packet", this.audioCallback); }; -AirTunesDevice.prototype.onSyncNeeded = function(seq) { +AirTunesDevice.prototype.onSyncNeeded = function (seq) { this.udpServers.sendControlSync(seq, this); //if ( this.airplay2)this.rtsp.sendControlSync(seq, this, this.rtsp); }; -AirTunesDevice.prototype.cleanup = function() { +AirTunesDevice.prototype.cleanup = function () { this.audioSocket = null; this.audioPacketHistory = null; - this.status = 'stopped'; - this.emit('status', 'stopped'); - console.log('stop'); - if(this.audioCallback) { - this.audioOut.removeListener('packet', this.audioCallback); + this.status = "stopped"; + this.emit("status", "stopped"); + console.log("stop"); + if (this.audioCallback) { + this.audioOut.removeListener("packet", this.audioCallback); this.audioCallback = null; } @@ -323,87 +384,110 @@ AirTunesDevice.prototype.cleanup = function() { this.rtsp = null; }; -AirTunesDevice.prototype.reportStatus = function(){ - this.emit('status', this.status); +AirTunesDevice.prototype.reportStatus = function () { + this.emit("status", this.status); }; -AirTunesDevice.prototype.stop = function(cb) { - try{ - this.rtsp.once('end', function() { - if(cb) - cb(); +AirTunesDevice.prototype.stop = function (cb) { + try { + this.rtsp.once("end", function () { + if (cb) cb(); }); - console.log('ted'); + console.log("ted"); this.rtsp.teardown(); - } catch(_){} + } catch (_) {} }; -AirTunesDevice.prototype.setVolume = function(volume, callback) { +AirTunesDevice.prototype.setVolume = function (volume, callback) { this.rtsp.setVolume(volume, callback); }; -AirTunesDevice.prototype.setTrackInfo = function(name, artist, album, callback) { +AirTunesDevice.prototype.setTrackInfo = function ( + name, + artist, + album, + callback +) { this.rtsp.setTrackInfo(name, artist, album, callback); }; -AirTunesDevice.prototype.setProgress = function(progress, duration, callback) { +AirTunesDevice.prototype.setProgress = function (progress, duration, callback) { this.rtsp.setProgress(progress, duration, callback); }; -AirTunesDevice.prototype.setArtwork = function(art, contentType, callback) { +AirTunesDevice.prototype.setArtwork = function (art, contentType, callback) { this.rtsp.setArtwork(art, contentType, callback); }; -AirTunesDevice.prototype.setPasscode = function(password) { +AirTunesDevice.prototype.setPasscode = function (password) { this.rtsp.setPasscode(password); }; -AirTunesDevice.prototype.requireEncryption = function() { +AirTunesDevice.prototype.requireEncryption = function () { return this.requireEncryption; }; module.exports = AirTunesDevice; - -function makeAirTunesPacket(packet, encoder, requireEncryption, alacEncoding = true, credentials = null, bindings, bindingsok) { +function makeAirTunesPacket( + packet, + encoder, + requireEncryption, + alacEncoding = true, + credentials = null, + bindings, + bindingsok +) { // console.log("alacEncoding2",alacEncoding) - var alac = (alacEncoding || credentials) ? pcmToALAC(encoder, packet.pcm, bindings, bindingsok) : pcmParse(encoder, packet.pcm); + var alac = + alacEncoding || credentials + ? pcmToALAC(encoder, packet.pcm, bindings, bindingsok) + : pcmParse(encoder, packet.pcm); var airTunes = new Buffer(alac.length + RTP_HEADER_SIZE); - header = makeRTPHeader(packet); - if(requireEncryption) { + header = makeRTPHeader(packet); + if (requireEncryption) { if (bindingsok) { - if (bindings == null) {bindings = require('../build/Release/airtunes');} - bindings.encryptAES(alac, alac.length); } else { - alac = encryptAES(alac,alac.length); + if (bindings == null) { + bindings = require("../build/Release/airtunes.node"); + } + bindings.encryptAES(alac, alac.length); + } else { + alac = encryptAES(alac, alac.length); } } if (credentials) { - let pcm = credentials.encryptAudio(alac,header.slice(4,12),packet.seq) + let pcm = credentials.encryptAudio(alac, header.slice(4, 12), packet.seq); let airplay = new Buffer(RTP_HEADER_SIZE + pcm.length); header.copy(airplay); pcm.copy(airplay, RTP_HEADER_SIZE); return airplay; // console.log(alac.length) - } else { - header.copy(airTunes); - alac.copy(airTunes, RTP_HEADER_SIZE); - return airTunes;} + } else { + header.copy(airTunes); + alac.copy(airTunes, RTP_HEADER_SIZE); + return airTunes; + } } - function pcmToALAC(encoder, pcmData, bindings, bindingsok) { var alacData = new Buffer(config.packet_size + 8); if (bindingsok == true) { - var alacSize = bindings.encodeALAC(encoder, pcmData, alacData, pcmData.length); + var alacSize = bindings.encodeALAC( + encoder, + pcmData, + alacData, + pcmData.length + ); return alacData.slice(0, alacSize); } else { - // I only did the actual computational part, the rest that I didn't do should be realitively simple to do. - let bsize = 352, frames = 352; // Set these to whatever they should be - const p = new Uint8Array((352 * 2 * 2) + 8); // p = *out; + // I only did the actual computational part, the rest that I didn't do should be realitively simple to do. + let bsize = 352, + frames = 352; // Set these to whatever they should be + const p = new Uint8Array(352 * 2 * 2 + 8); // p = *out; const input = new Uint32Array(pcmData.length / 4); let j = 0; - for (let i = 0; i < pcmData.length; i+=4) { + for (let i = 0; i < pcmData.length; i += 4) { let res = pcmData[i]; res |= pcmData[i + 1] << 8; res |= pcmData[i + 2] << 16; @@ -411,7 +495,8 @@ function pcmToALAC(encoder, pcmData, bindings, bindingsok) { input[j++] = res; } // uint32_t *in = (uint32_t*) sample; - let pindex = 0, iindex = 0; + let pindex = 0, + iindex = 0; p[pindex++] = 1 << 5; // 0b100000 p[pindex++] = 0; @@ -420,12 +505,12 @@ function pcmToALAC(encoder, pcmData, bindings, bindingsok) { // bXX--bYY = bits XX to YY of bsize // So we basically just splitting bsize into the individual byte values and storing them in p // We've also shifted everything to the left by one (hence why we need the bit from bsize above) - p[pindex++] = ((bsize & 0x7f800000) << 1) >>> 24; // b30--b23 - p[pindex++] = ((bsize & 0x007f8000) << 1) >>> 16; // b22--b15 - p[pindex++] = ((bsize & 0x00007f80) << 1) >>> 8; // b14--b7 - p[pindex] = ((bsize & 0x0000007f) << 1); // b6--b0 + p[pindex++] = ((bsize & 0x7f800000) << 1) >>> 24; // b30--b23 + p[pindex++] = ((bsize & 0x007f8000) << 1) >>> 16; // b22--b15 + p[pindex++] = ((bsize & 0x00007f80) << 1) >>> 8; // b14--b7 + p[pindex] = (bsize & 0x0000007f) << 1; // b6--b0 // And this is why we shifted the bits to the left. - p[pindex++] |= (input[iindex] & 0x00008000) >>> 15; // b7 from in[iindex] + p[pindex++] |= (input[iindex] & 0x00008000) >>> 15; // b7 from in[iindex] let count = frames - 1; @@ -434,18 +519,19 @@ function pcmToALAC(encoder, pcmData, bindings, bindingsok) { // This is weird lmao. Everything that we're adding has been shifted left by one. // And here, we're soring the lower 16 bits then the higher 16 bits. - p[pindex++] = ((i & 0x00007f80) >>> 7); // b14--b7 + p[pindex++] = (i & 0x00007f80) >>> 7; // b14--b7 p[pindex++] = ((i & 0x0000007f) << 1) | ((i & 0x80000000) >>> 31); // b6--b0, b31 - p[pindex++] = ((i & 0x7f800000) >>> 23); // b30--b23 - p[pindex++] = ((i & 0x007f0000) >>> 15) | ((input[iindex] & 0x00008000) >> 15);// b16--b15, b7 from in[pindex] + p[pindex++] = (i & 0x7f800000) >>> 23; // b30--b23 + p[pindex++] = + ((i & 0x007f0000) >>> 15) | ((input[iindex] & 0x00008000) >> 15); // b16--b15, b7 from in[pindex] } // Last Sample let i = input[iindex]; - p[pindex++] = ((i & 0x00007f80) >>> 7); // b14--b7 + p[pindex++] = (i & 0x00007f80) >>> 7; // b14--b7 p[pindex++] = ((i & 0x0000007f) << 1) | ((i & 0x80000000) >>> 31); // b6--b0, b31 - p[pindex++] = ((i & 0x7f800000) >>> 23); // b30--b23 - p[pindex++] = ((i & 0x007f0000) >>> 15); // b16--b15, 0 as last bit because we have no more data after this + p[pindex++] = (i & 0x7f800000) >>> 23; // b30--b23 + p[pindex++] = (i & 0x007f0000) >>> 15; // b16--b15, 0 as last bit because we have no more data after this // When we've read all we can from in, we need to fill the remaining space in p with 0's count = (bsize - frames) * 4; @@ -458,62 +544,60 @@ function pcmToALAC(encoder, pcmData, bindings, bindingsok) { // const size = pindex; const alacSize = pindex; // Should be right - alacData = Buffer.from(p.buffer) + alacData = Buffer.from(p.buffer); return alacData.slice(0, alacSize); } //alacData = new Buffer(p); //var alacSize = bindings.encodeALAC(encoder, pcmData, alacData, pcmData.length); // console.log(alacData) - - - - } function pcmParse(encoder, pcmData) { - let dst = new Uint8Array(352 * 4); - let src = pcmData; - - let a = b = 0; - let size; - for (size = 0; size < 352; size++) { - dst[a++] = src[b + 1]; - dst[a++] = src[b++]; - b++; - - dst[a++] = src[b + 1]; - dst[a++] = src[b++]; - b++; - } - return Buffer.from(dst); + let dst = new Uint8Array(352 * 4); + let src = pcmData; + + let a = (b = 0); + let size; + for (size = 0; size < 352; size++) { + dst[a++] = src[b + 1]; + dst[a++] = src[b++]; + b++; + + dst[a++] = src[b + 1]; + dst[a++] = src[b++]; + b++; + } + return Buffer.from(dst); } function encryptAES(alacData, alacSize) { - let result = new Buffer.concat([]) - const isv = Buffer.from([0x78, 0xf4, 0x41, 0x2c, 0x8d, 0x17, 0x37, 0x90, 0x2b, 0x15, 0xa6, 0xb3, 0xee, 0x77, 0x0d, 0x67]); - const aes_key = Buffer.from([0x14, 0x49, 0x7d, 0xcc, 0x98, 0xe1, 0x37, 0xa8, 0x55, 0xc1, 0x45, 0x5a, 0x6b, 0xc0, 0xc9, 0x79]); - let remainder = alacData.length % 16 + let result = new Buffer.concat([]); + const isv = Buffer.from([ + 0x78, 0xf4, 0x41, 0x2c, 0x8d, 0x17, 0x37, 0x90, 0x2b, 0x15, 0xa6, 0xb3, + 0xee, 0x77, 0x0d, 0x67, + ]); + const aes_key = Buffer.from([ + 0x14, 0x49, 0x7d, 0xcc, 0x98, 0xe1, 0x37, 0xa8, 0x55, 0xc1, 0x45, 0x5a, + 0x6b, 0xc0, 0xc9, 0x79, + ]); + let remainder = alacData.length % 16; let end_of_encoded_data = alacData.length - remainder; - let cipher = crypto.createCipheriv('aes-128-cbc', aes_key, isv); - cipher.setAutoPadding(false); + let cipher = crypto.createCipheriv("aes-128-cbc", aes_key, isv); + cipher.setAutoPadding(false); - for (i = 0, l = end_of_encoded_data - 16; i <= l; i += 16) { - let chunk = cipher.update(alacData.slice(i,i+16)) - result = Buffer.concat([result,chunk]) - } + for (i = 0, l = end_of_encoded_data - 16; i <= l; i += 16) { + let chunk = cipher.update(alacData.slice(i, i + 16)); + result = Buffer.concat([result, chunk]); + } return Buffer.concat([result, alacData.slice(end_of_encoded_data)]); } - - function makeRTPHeader(packet) { var header = new Buffer(RTP_HEADER_SIZE); - if(packet.seq === 0) - header.writeUInt16BE(0x80e0, 0); - else - header.writeUInt16BE(0x8060, 0); + if (packet.seq === 0) header.writeUInt16BE(0x80e0, 0); + else header.writeUInt16BE(0x8060, 0); header.writeUInt16BE(nu.low16(packet.seq), 2); diff --git a/package.json b/package.json index 13d931b..bbb8f2b 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "exe": "pkg --compress Brotli .", "exefast": "pkg .", "vaportest": "node examples\\play_radio.js --host 192.168.100.12 --port 7000 --debug true --mode 1 --airplay2 1", - "izanami": "node ./izanami.js" + "izanami": "node ./izanami.js", + "izanami:build": "node ./scripts/esbuild.cjs" }, "licenses": [ { @@ -81,10 +82,12 @@ "yargs": "^17.7.2" }, "devDependencies": { + "esbuild": "^0.21.5", "node-abi": "^3.56.0", "optimist": "^0.6.1", "prebuild": "ciderapp/prebuild", "request": "^2.88.2", + "rollup": "^4.18.0", "run-script-os": "^1.1.6" }, "bugs": { diff --git a/scripts/esbuild.cjs b/scripts/esbuild.cjs new file mode 100644 index 0000000..bb286fc --- /dev/null +++ b/scripts/esbuild.cjs @@ -0,0 +1,17 @@ +// @ts-check + +async function main() { + await require("esbuild").build({ + entryPoints: ["./izanami.js"], + bundle: true, + platform: "node", + outdir: "dist", + external: ['./build'], + loader: { + '.node': 'file', + }, + // minify: true, + // minifyIdentifiers: true, + }); +} +main(); diff --git a/yarn.lock b/yarn.lock index 37be945..8d560cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -62,6 +62,167 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@gar/promisify@npm:^1.0.1": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -281,6 +442,118 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.18.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-android-arm64@npm:4.18.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.18.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.18.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.18.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.18.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.18.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.18.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.18.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.18.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.18.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.18.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.18.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.18.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.18.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.18.0": + version: 4.18.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.18.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@tootallnate/once@npm:1": version: 1.1.2 resolution: "@tootallnate/once@npm:1.1.2" @@ -288,6 +561,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:1.0.5": + version: 1.0.5 + resolution: "@types/estree@npm:1.0.5" + checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d + languageName: node + linkType: hard + "@types/long@npm:^4.0.1": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -427,6 +707,7 @@ __metadata: curve25519-js: "npm:^0.0.4" electron-fetch: "npm:^1.9.1" elliptic: "npm:^6.5.5" + esbuild: "npm:^0.21.5" fast-srp-hap: "npm:^2.0.4" js-crypto-aes: "npm:^1.0.6" js-sha1: "npm:^0.7.0" @@ -440,6 +721,7 @@ __metadata: prebuild-install: ciderapp/prebuild-install python-struct: "npm:^1.1.3" request: "npm:^2.88.2" + rollup: "npm:^4.18.0" run-script-os: "npm:^1.1.6" simple-plist: "npm:^1.4.0" varint: "npm:^6.0.0" @@ -1368,6 +1650,86 @@ chacha-js@ciderapp/chacha20poly1305: languageName: node linkType: hard +"esbuild@npm:^0.21.5": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.5" + "@esbuild/android-arm": "npm:0.21.5" + "@esbuild/android-arm64": "npm:0.21.5" + "@esbuild/android-x64": "npm:0.21.5" + "@esbuild/darwin-arm64": "npm:0.21.5" + "@esbuild/darwin-x64": "npm:0.21.5" + "@esbuild/freebsd-arm64": "npm:0.21.5" + "@esbuild/freebsd-x64": "npm:0.21.5" + "@esbuild/linux-arm": "npm:0.21.5" + "@esbuild/linux-arm64": "npm:0.21.5" + "@esbuild/linux-ia32": "npm:0.21.5" + "@esbuild/linux-loong64": "npm:0.21.5" + "@esbuild/linux-mips64el": "npm:0.21.5" + "@esbuild/linux-ppc64": "npm:0.21.5" + "@esbuild/linux-riscv64": "npm:0.21.5" + "@esbuild/linux-s390x": "npm:0.21.5" + "@esbuild/linux-x64": "npm:0.21.5" + "@esbuild/netbsd-x64": "npm:0.21.5" + "@esbuild/openbsd-x64": "npm:0.21.5" + "@esbuild/sunos-x64": "npm:0.21.5" + "@esbuild/win32-arm64": "npm:0.21.5" + "@esbuild/win32-ia32": "npm:0.21.5" + "@esbuild/win32-x64": "npm:0.21.5" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.2 resolution: "escalade@npm:3.1.2" @@ -1604,6 +1966,25 @@ chacha-js@ciderapp/chacha20poly1305: languageName: node linkType: hard +"fsevents@npm:~2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "fstream@npm:^1.0.0, fstream@npm:^1.0.12, fstream@npm:~1.0.10": version: 1.0.12 resolution: "fstream@npm:1.0.12" @@ -2739,7 +3120,7 @@ mdns-js@ciderapp/node-mdns-js: languageName: node linkType: hard -"node-gyp@npm:^10.1.0": +"node-gyp@npm:^10.1.0, node-gyp@npm:latest": version: 10.1.0 resolution: "node-gyp@npm:10.1.0" dependencies: @@ -3477,6 +3858,69 @@ prebuild@ciderapp/prebuild: languageName: node linkType: hard +"rollup@npm:^4.18.0": + version: 4.18.0 + resolution: "rollup@npm:4.18.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.18.0" + "@rollup/rollup-android-arm64": "npm:4.18.0" + "@rollup/rollup-darwin-arm64": "npm:4.18.0" + "@rollup/rollup-darwin-x64": "npm:4.18.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.18.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.18.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.18.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.18.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.18.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.18.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.18.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.18.0" + "@rollup/rollup-linux-x64-musl": "npm:4.18.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.18.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.18.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.18.0" + "@types/estree": "npm:1.0.5" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/7d0239f029c48d977e0d0b942433bed9ca187d2328b962fc815fc775d0fdf1966ffcd701fef265477e999a1fb01bddcc984fc675d1b9d9864bf8e1f1f487e23e + languageName: node + linkType: hard + "rsvp@npm:^3.0.13": version: 3.6.2 resolution: "rsvp@npm:3.6.2"