pinokiod 3.146.0 → 3.147.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/kernel/util.js +16 -182
- package/package.json +1 -1
package/kernel/util.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
|
-
const { spawn
|
|
2
|
+
const { spawn } = require('child_process')
|
|
3
3
|
const Clipboard = require('copy-paste/promises');
|
|
4
4
|
const http = require('http');
|
|
5
5
|
const notifier = require('toasted-notifier');
|
|
@@ -12,9 +12,6 @@ const retry = require('async-retry');
|
|
|
12
12
|
const child_process = require('node:child_process');
|
|
13
13
|
const {auto: normalizeEOL} = require("eol");
|
|
14
14
|
const {EOL} = require("os");
|
|
15
|
-
const axios = require('axios');
|
|
16
|
-
const { pipeline } = require('stream/promises');
|
|
17
|
-
const { pathToFileURL } = require('url');
|
|
18
15
|
const { randomUUID } = require('crypto');
|
|
19
16
|
const fsp = fs.promises;
|
|
20
17
|
const breakPattern = /\n/g;
|
|
@@ -114,22 +111,6 @@ function resolvePublicAssetUrl(filePath) {
|
|
|
114
111
|
return null
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
function soundTargetToClientUrl(target) {
|
|
118
|
-
if (!target) {
|
|
119
|
-
return null
|
|
120
|
-
}
|
|
121
|
-
if (target.kind === 'url') {
|
|
122
|
-
return target.value
|
|
123
|
-
}
|
|
124
|
-
if (target.kind === 'file') {
|
|
125
|
-
const resolved = resolveAsarPath(path.resolve(target.value))
|
|
126
|
-
if (resolved === DEFAULT_CHIME_PATH) {
|
|
127
|
-
return DEFAULT_CHIME_URL_PATH
|
|
128
|
-
}
|
|
129
|
-
return resolvePublicAssetUrl(resolved)
|
|
130
|
-
}
|
|
131
|
-
return null
|
|
132
|
-
}
|
|
133
114
|
function ensureNotifierBinaries() {
|
|
134
115
|
if (platform !== 'darwin') {
|
|
135
116
|
return
|
|
@@ -662,146 +643,6 @@ function fill_object(obj, pattern, list, cache) {
|
|
|
662
643
|
}
|
|
663
644
|
const isNonEmptyString = (value) => typeof value === 'string' && value.trim().length > 0
|
|
664
645
|
const HTTP_URL_REGEX = /^https?:\/\//i
|
|
665
|
-
const linuxSoundCandidates = [
|
|
666
|
-
{ cmd: 'paplay', args: (filePath) => [filePath] },
|
|
667
|
-
{ cmd: 'aplay', args: (filePath) => [filePath] },
|
|
668
|
-
{ cmd: 'ffplay', args: (filePath) => ['-autoexit', '-nodisp', filePath] },
|
|
669
|
-
{ cmd: 'mpg123', args: (filePath) => [filePath] },
|
|
670
|
-
{ cmd: 'mplayer', args: (filePath) => [filePath] },
|
|
671
|
-
{ cmd: 'cvlc', args: (filePath) => ['--play-and-exit', filePath] },
|
|
672
|
-
]
|
|
673
|
-
let cachedLinuxSoundPlayer
|
|
674
|
-
const DEFAULT_CHIME_PATH = resolveAsarPath(path.resolve(__dirname, '../server/public/chime.mp3'))
|
|
675
|
-
function pickLinuxSoundPlayer() {
|
|
676
|
-
if (cachedLinuxSoundPlayer !== undefined) {
|
|
677
|
-
return cachedLinuxSoundPlayer
|
|
678
|
-
}
|
|
679
|
-
for (const candidate of linuxSoundCandidates) {
|
|
680
|
-
try {
|
|
681
|
-
const lookup = spawnSync('which', [candidate.cmd], { stdio: 'ignore' })
|
|
682
|
-
if (lookup.status === 0) {
|
|
683
|
-
cachedLinuxSoundPlayer = candidate
|
|
684
|
-
return cachedLinuxSoundPlayer
|
|
685
|
-
}
|
|
686
|
-
} catch (_) {
|
|
687
|
-
// ignore lookup errors and try next candidate
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
cachedLinuxSoundPlayer = null
|
|
691
|
-
return cachedLinuxSoundPlayer
|
|
692
|
-
}
|
|
693
|
-
function extractUrlExtension(rawUrl) {
|
|
694
|
-
try {
|
|
695
|
-
const parsed = new URL(rawUrl)
|
|
696
|
-
const ext = path.extname(parsed.pathname)
|
|
697
|
-
if (ext && ext.length <= 6) {
|
|
698
|
-
return ext
|
|
699
|
-
}
|
|
700
|
-
} catch (_) {
|
|
701
|
-
// ignore URL parse errors
|
|
702
|
-
}
|
|
703
|
-
return ''
|
|
704
|
-
}
|
|
705
|
-
async function downloadSoundToTemp(url) {
|
|
706
|
-
const tempPath = path.join(os.tmpdir(), `pinokio-notify-${randomUUID()}${extractUrlExtension(url)}`)
|
|
707
|
-
const response = await axios.get(url, {
|
|
708
|
-
responseType: 'stream',
|
|
709
|
-
timeout: 15000,
|
|
710
|
-
})
|
|
711
|
-
await pipeline(response.data, fs.createWriteStream(tempPath))
|
|
712
|
-
return {
|
|
713
|
-
filePath: tempPath,
|
|
714
|
-
cleanup: async () => {
|
|
715
|
-
try {
|
|
716
|
-
await fsp.unlink(tempPath)
|
|
717
|
-
} catch (_) {
|
|
718
|
-
// best-effort cleanup
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
function playSoundFile(filePath) {
|
|
724
|
-
if (!filePath) {
|
|
725
|
-
return null
|
|
726
|
-
}
|
|
727
|
-
if (platform === 'darwin') {
|
|
728
|
-
return spawn('afplay', [resolveAsarPath(filePath)], { stdio: 'ignore' })
|
|
729
|
-
}
|
|
730
|
-
if (platform === 'win32') {
|
|
731
|
-
const fileUrl = pathToFileURL(resolveAsarPath(filePath)).href.replace(/'/g, "''")
|
|
732
|
-
const script = [
|
|
733
|
-
"$ErrorActionPreference = 'Stop'",
|
|
734
|
-
"Add-Type -AssemblyName presentationCore",
|
|
735
|
-
"$player = New-Object system.windows.media.mediaplayer",
|
|
736
|
-
`$player.Open([Uri]'${fileUrl}')`,
|
|
737
|
-
"$player.Play()",
|
|
738
|
-
"while ($player.NaturalDuration.HasTimeSpan -eq $false) { Start-Sleep -Milliseconds 100 }",
|
|
739
|
-
"Start-Sleep -Seconds $player.NaturalDuration.TimeSpan.TotalSeconds",
|
|
740
|
-
].join('; ')
|
|
741
|
-
return spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-WindowStyle', 'Hidden', '-Command', script], {
|
|
742
|
-
stdio: 'ignore'
|
|
743
|
-
})
|
|
744
|
-
}
|
|
745
|
-
const candidate = pickLinuxSoundPlayer()
|
|
746
|
-
if (!candidate) {
|
|
747
|
-
throw new Error('No supported audio player found for Linux (expected one of paplay, aplay, ffplay, mpg123, mplayer, cvlc).')
|
|
748
|
-
}
|
|
749
|
-
const preparedPath = resolveAsarPath(filePath)
|
|
750
|
-
return spawn(candidate.cmd, candidate.args(preparedPath), { stdio: 'ignore' })
|
|
751
|
-
}
|
|
752
|
-
function scheduleSoundPlayback(target) {
|
|
753
|
-
if (!target) {
|
|
754
|
-
return
|
|
755
|
-
}
|
|
756
|
-
;(async () => {
|
|
757
|
-
let cleanup = null
|
|
758
|
-
try {
|
|
759
|
-
let filePath
|
|
760
|
-
if (target.kind === 'file') {
|
|
761
|
-
filePath = resolveAsarPath(target.value)
|
|
762
|
-
} else if (target.kind === 'url') {
|
|
763
|
-
if (!HTTP_URL_REGEX.test(target.value)) {
|
|
764
|
-
throw new Error(`Unsupported sound URL: ${target.value}`)
|
|
765
|
-
}
|
|
766
|
-
const downloaded = await downloadSoundToTemp(target.value)
|
|
767
|
-
filePath = downloaded.filePath
|
|
768
|
-
cleanup = downloaded.cleanup
|
|
769
|
-
} else {
|
|
770
|
-
return
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
const child = playSoundFile(filePath)
|
|
774
|
-
if (!child) {
|
|
775
|
-
if (cleanup) {
|
|
776
|
-
cleanup().catch(() => {})
|
|
777
|
-
}
|
|
778
|
-
return
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
const tidy = () => {
|
|
782
|
-
if (!cleanup) {
|
|
783
|
-
return
|
|
784
|
-
}
|
|
785
|
-
cleanup().catch((err) => {
|
|
786
|
-
console.error('Failed to clean up temporary sound file:', err)
|
|
787
|
-
})
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
child.once('close', tidy)
|
|
791
|
-
child.once('error', tidy)
|
|
792
|
-
child.on('error', (err) => {
|
|
793
|
-
console.error('Failed to play notification sound:', err)
|
|
794
|
-
})
|
|
795
|
-
} catch (err) {
|
|
796
|
-
if (cleanup) {
|
|
797
|
-
cleanup().catch(() => {})
|
|
798
|
-
}
|
|
799
|
-
console.error('Failed to play notification sound:', err)
|
|
800
|
-
}
|
|
801
|
-
})().catch((err) => {
|
|
802
|
-
console.error('Failed to play notification sound:', err)
|
|
803
|
-
})
|
|
804
|
-
}
|
|
805
646
|
function push(params) {
|
|
806
647
|
/*
|
|
807
648
|
params :- {
|
|
@@ -813,19 +654,21 @@ function push(params) {
|
|
|
813
654
|
}
|
|
814
655
|
*/
|
|
815
656
|
const notifyParams = { ...(params || {}) }
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
657
|
+
const requestedSound = notifyParams.sound
|
|
658
|
+
let clientSoundUrl = null
|
|
659
|
+
|
|
660
|
+
if (requestedSound === true) {
|
|
661
|
+
clientSoundUrl = DEFAULT_CHIME_URL_PATH
|
|
662
|
+
} else if (isNonEmptyString(requestedSound)) {
|
|
663
|
+
const trimmed = requestedSound.trim()
|
|
664
|
+
if (HTTP_URL_REGEX.test(trimmed)) {
|
|
665
|
+
clientSoundUrl = trimmed
|
|
666
|
+
} else {
|
|
667
|
+
console.warn(`Ignoring notification sound (expected http/https URL): ${trimmed}`)
|
|
826
668
|
}
|
|
827
|
-
|
|
828
|
-
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
notifyParams.sound = false
|
|
829
672
|
|
|
830
673
|
if (notifyParams.image && !notifyParams.contentImage) {
|
|
831
674
|
notifyParams.contentImage = notifyParams.image
|
|
@@ -854,15 +697,6 @@ function push(params) {
|
|
|
854
697
|
notifyParams.appID = WINDOWS_TOAST_APP_ID
|
|
855
698
|
}
|
|
856
699
|
}
|
|
857
|
-
const clientSoundUrl = soundTargetToClientUrl(customSoundTarget)
|
|
858
|
-
if (customSoundTarget) {
|
|
859
|
-
notifyParams.sound = false
|
|
860
|
-
const shouldPlayLocally = platform !== 'win32' || !clientSoundUrl
|
|
861
|
-
if (shouldPlayLocally) {
|
|
862
|
-
scheduleSoundPlayback(customSoundTarget)
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
|
|
866
700
|
const clientImage = resolvePublicAssetUrl(notifyParams.contentImage) || resolvePublicAssetUrl(notifyParams.image)
|
|
867
701
|
const eventPayload = {
|
|
868
702
|
id: randomUUID(),
|
|
@@ -870,7 +704,7 @@ function push(params) {
|
|
|
870
704
|
subtitle: notifyParams.subtitle || null,
|
|
871
705
|
message: notifyParams.message || '',
|
|
872
706
|
image: clientImage,
|
|
873
|
-
sound: clientSoundUrl,
|
|
707
|
+
sound: clientSoundUrl || null,
|
|
874
708
|
timestamp: Date.now(),
|
|
875
709
|
platform,
|
|
876
710
|
}
|