pinokiod 3.170.0 → 3.181.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/favicon.js +91 -34
- package/kernel/peer.js +73 -0
- package/kernel/util.js +13 -2
- package/package.json +1 -1
- package/server/index.js +249 -26
- package/server/public/common.js +244 -0
- package/server/public/files-app/app.css +73 -1
- package/server/public/files-app/app.js +255 -2
- package/server/public/layout.js +115 -1
- package/server/public/nav.js +227 -64
- package/server/public/style.css +27 -3
- package/server/public/tab-idle-notifier.js +3 -0
- package/server/routes/files.js +96 -0
- package/server/socket.js +71 -4
- package/server/views/app.ejs +603 -53
- package/server/views/connect.ejs +9 -0
- package/server/views/file_browser.ejs +9 -2
- package/server/views/index.ejs +11 -2
- package/server/views/init/index.ejs +9 -2
- package/server/views/layout.ejs +7 -5
- package/server/views/net.ejs +9 -0
- package/server/views/network.ejs +9 -0
- package/server/views/review.ejs +4 -3
- package/server/views/screenshots.ejs +9 -0
- package/server/views/settings.ejs +9 -0
- package/server/views/terminals.ejs +12 -3
- package/server/views/tools.ejs +10 -1
package/kernel/favicon.js
CHANGED
|
@@ -1,55 +1,112 @@
|
|
|
1
1
|
const axios = require('axios')
|
|
2
|
-
const { URL } = require(
|
|
3
|
-
const { JSDOM } = require(
|
|
2
|
+
const { URL } = require('url')
|
|
3
|
+
const { JSDOM } = require('jsdom')
|
|
4
4
|
|
|
5
5
|
class Favicon {
|
|
6
|
+
constructor(opts = {}) {
|
|
7
|
+
this.cache = new Map() // origin -> { url: string|null, ts: number }
|
|
8
|
+
this.ttlMs = opts.ttlMs || (10 * 60 * 1000) // 10 minutes
|
|
9
|
+
this.headTimeoutMs = opts.headTimeoutMs || 800
|
|
10
|
+
this.getTimeoutMs = opts.getTimeoutMs || 700
|
|
11
|
+
this.totalBudgetMs = opts.totalBudgetMs || 900
|
|
12
|
+
this.commonPaths = Array.isArray(opts.commonPaths) && opts.commonPaths.length > 0
|
|
13
|
+
? opts.commonPaths
|
|
14
|
+
: ['/favicon.ico', '/favicon.png']
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_now() {
|
|
18
|
+
return Date.now()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_getCache(origin) {
|
|
22
|
+
const entry = this.cache.get(origin)
|
|
23
|
+
if (!entry) return null
|
|
24
|
+
if (this._now() - entry.ts > this.ttlMs) {
|
|
25
|
+
this.cache.delete(origin)
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
return entry.url
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_setCache(origin, url) {
|
|
32
|
+
this.cache.set(origin, { url: url || null, ts: this._now() })
|
|
33
|
+
}
|
|
34
|
+
|
|
6
35
|
async get(pageUrl) {
|
|
7
|
-
let origin
|
|
36
|
+
let origin
|
|
8
37
|
try {
|
|
9
|
-
origin = new URL(pageUrl).origin
|
|
38
|
+
origin = new URL(pageUrl).origin
|
|
10
39
|
} catch {
|
|
11
|
-
throw new Error(
|
|
40
|
+
throw new Error('Invalid URL: ' + pageUrl)
|
|
12
41
|
}
|
|
13
42
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
// 1. Try common favicon paths first
|
|
23
|
-
for (const path of candidates) {
|
|
24
|
-
const fullUrl = origin + path;
|
|
25
|
-
if (await this.checkImageUrl(fullUrl)) return fullUrl;
|
|
43
|
+
const cached = this._getCache(origin)
|
|
44
|
+
if (typeof cached !== 'undefined' && cached !== null) {
|
|
45
|
+
return cached
|
|
46
|
+
} else if (cached === null) {
|
|
47
|
+
// we cached a miss previously
|
|
48
|
+
return null
|
|
26
49
|
}
|
|
27
50
|
|
|
28
|
-
|
|
51
|
+
const start = this._now()
|
|
52
|
+
const withinBudget = () => (this._now() - start) < this.totalBudgetMs
|
|
53
|
+
|
|
54
|
+
// 1) Try common paths (limited set) in parallel, with short HEAD timeouts
|
|
29
55
|
try {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (await this.checkImageUrl(resolvedUrl)) return resolvedUrl;
|
|
56
|
+
const attempts = this.commonPaths.map((p) => this.checkImageUrl(origin + p, this.headTimeoutMs))
|
|
57
|
+
const results = await Promise.all(attempts)
|
|
58
|
+
for (let i = 0; i < results.length; i++) {
|
|
59
|
+
if (results[i]) {
|
|
60
|
+
const url = origin + this.commonPaths[i]
|
|
61
|
+
this._setCache(origin, url)
|
|
62
|
+
return url
|
|
63
|
+
}
|
|
39
64
|
}
|
|
40
|
-
} catch {
|
|
41
|
-
|
|
65
|
+
} catch (_) {}
|
|
66
|
+
|
|
67
|
+
if (!withinBudget()) {
|
|
68
|
+
this._setCache(origin, null)
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2) Fallback: fetch the page quickly, parse <link rel="icon">, then HEAD the first 1-2 candidates
|
|
73
|
+
try {
|
|
74
|
+
const res = await axios.get(pageUrl, { timeout: this.getTimeoutMs })
|
|
75
|
+
const dom = new JSDOM(res.data)
|
|
76
|
+
const nodes = Array.from(dom.window.document.querySelectorAll("link[rel~='icon'], link[rel='apple-touch-icon']"))
|
|
77
|
+
const hrefs = []
|
|
78
|
+
for (const node of nodes) {
|
|
79
|
+
const href = node.getAttribute('href')
|
|
80
|
+
if (href && typeof href === 'string') {
|
|
81
|
+
try {
|
|
82
|
+
const resolved = new URL(href, origin).href
|
|
83
|
+
hrefs.push(resolved)
|
|
84
|
+
} catch (_) {}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Try at most 2 parsed icons within budget
|
|
88
|
+
for (let i = 0; i < Math.min(2, hrefs.length); i++) {
|
|
89
|
+
if (!withinBudget()) break
|
|
90
|
+
const ok = await this.checkImageUrl(hrefs[i], this.headTimeoutMs)
|
|
91
|
+
if (ok) {
|
|
92
|
+
this._setCache(origin, hrefs[i])
|
|
93
|
+
return hrefs[i]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (_) {
|
|
97
|
+
// ignore parse failures
|
|
42
98
|
}
|
|
43
99
|
|
|
44
|
-
|
|
100
|
+
this._setCache(origin, null)
|
|
101
|
+
return null
|
|
45
102
|
}
|
|
46
103
|
|
|
47
|
-
async checkImageUrl(url) {
|
|
104
|
+
async checkImageUrl(url, timeoutOverride) {
|
|
48
105
|
try {
|
|
49
|
-
const res = await axios.head(url, { timeout:
|
|
50
|
-
return res.status === 200 && res.headers[
|
|
106
|
+
const res = await axios.head(url, { timeout: timeoutOverride || this.headTimeoutMs })
|
|
107
|
+
return res.status === 200 && (res.headers['content-type'] || '').startsWith('image/')
|
|
51
108
|
} catch {
|
|
52
|
-
return false
|
|
109
|
+
return false
|
|
53
110
|
}
|
|
54
111
|
}
|
|
55
112
|
}
|
package/kernel/peer.js
CHANGED
|
@@ -337,6 +337,79 @@ class PeerDiscovery {
|
|
|
337
337
|
return []
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
+
async router_info_lite() {
|
|
341
|
+
try {
|
|
342
|
+
let processes = []
|
|
343
|
+
if (this.info && this.info[this.host]) {
|
|
344
|
+
let procs = this.info[this.host].proc
|
|
345
|
+
let router = this.info[this.host].router
|
|
346
|
+
let port_mapping = this.info[this.host].port_mapping
|
|
347
|
+
for (let proc of procs) {
|
|
348
|
+
let chunks = (proc.ip || '').split(":")
|
|
349
|
+
let internal_port = chunks[chunks.length - 1]
|
|
350
|
+
let internal_host = chunks.slice(0, chunks.length - 1).join(":")
|
|
351
|
+
let external_port = port_mapping ? port_mapping[internal_port] : undefined
|
|
352
|
+
let external_ip = external_port ? `${this.host}:${external_port}` : undefined
|
|
353
|
+
|
|
354
|
+
let internal_router = []
|
|
355
|
+
// Check common local keys
|
|
356
|
+
const keys = [
|
|
357
|
+
`127.0.0.1:${proc.port}`,
|
|
358
|
+
`0.0.0.0:${proc.port}`,
|
|
359
|
+
`localhost:${proc.port}`,
|
|
360
|
+
]
|
|
361
|
+
for (const key of keys) {
|
|
362
|
+
if (router && router[key]) {
|
|
363
|
+
internal_router = internal_router.concat(router[key])
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const info = {
|
|
368
|
+
external_router: (router && external_ip && router[external_ip]) ? router[external_ip] : [],
|
|
369
|
+
internal_router,
|
|
370
|
+
external_ip,
|
|
371
|
+
external_port: external_port ? parseInt(external_port) : undefined,
|
|
372
|
+
internal_port: internal_port ? parseInt(internal_port) : undefined,
|
|
373
|
+
...proc,
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// In custom-domain mode, ensure external_router has something meaningful
|
|
377
|
+
const usingCustomDomain = this.kernel.router_kind === 'custom-domain'
|
|
378
|
+
if (usingCustomDomain) {
|
|
379
|
+
if (!info.external_router || info.external_router.length === 0) {
|
|
380
|
+
const fallbackKeys = new Set([
|
|
381
|
+
proc.ip,
|
|
382
|
+
`${internal_host}:${proc.port}`,
|
|
383
|
+
`127.0.0.1:${proc.port}`,
|
|
384
|
+
`0.0.0.0:${proc.port}`,
|
|
385
|
+
`localhost:${proc.port}`
|
|
386
|
+
])
|
|
387
|
+
for (const key of fallbackKeys) {
|
|
388
|
+
if (key && router && router[key] && router[key].length > 0) {
|
|
389
|
+
info.external_router = router[key]
|
|
390
|
+
break
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (info.external_router && info.external_router.length > 0) {
|
|
395
|
+
info.external_router = Array.from(new Set(info.external_router))
|
|
396
|
+
} else if (internal_router.length > 0) {
|
|
397
|
+
info.external_router = Array.from(new Set(internal_router))
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
processes.push(info)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
processes.sort((a, b) => {
|
|
405
|
+
return (b.external_port || 0) - (a.external_port || 0)
|
|
406
|
+
})
|
|
407
|
+
return processes
|
|
408
|
+
} catch (e) {
|
|
409
|
+
console.log('router_info_lite ERROR', e)
|
|
410
|
+
return []
|
|
411
|
+
}
|
|
412
|
+
}
|
|
340
413
|
async installed() {
|
|
341
414
|
let folders = await fs.promises.readdir(this.kernel.path("api"))
|
|
342
415
|
let installed = []
|
package/kernel/util.js
CHANGED
|
@@ -698,6 +698,8 @@ function push(params) {
|
|
|
698
698
|
}
|
|
699
699
|
}
|
|
700
700
|
const clientImage = resolvePublicAssetUrl(notifyParams.contentImage) || resolvePublicAssetUrl(notifyParams.image)
|
|
701
|
+
const deviceId = (typeof notifyParams.device_id === 'string' && notifyParams.device_id.trim()) ? notifyParams.device_id.trim() : null
|
|
702
|
+
const audience = (typeof notifyParams.audience === 'string' && notifyParams.audience.trim()) ? notifyParams.audience.trim() : null
|
|
701
703
|
const eventPayload = {
|
|
702
704
|
id: randomUUID(),
|
|
703
705
|
title: notifyParams.title,
|
|
@@ -707,11 +709,20 @@ function push(params) {
|
|
|
707
709
|
sound: clientSoundUrl || null,
|
|
708
710
|
timestamp: Date.now(),
|
|
709
711
|
platform,
|
|
712
|
+
device_id: deviceId,
|
|
713
|
+
audience,
|
|
710
714
|
}
|
|
711
715
|
|
|
712
716
|
emitPushEvent(eventPayload)
|
|
713
|
-
|
|
714
|
-
|
|
717
|
+
// Suppress host OS notification when explicitly disabled (e.g., device-scoped pushes)
|
|
718
|
+
const shouldNotifyHost = notifyParams.host !== false
|
|
719
|
+
if (shouldNotifyHost) {
|
|
720
|
+
const notifyCopy = { ...notifyParams }
|
|
721
|
+
delete notifyCopy.host
|
|
722
|
+
delete notifyCopy.device_id
|
|
723
|
+
delete notifyCopy.audience
|
|
724
|
+
notifier.notify(notifyCopy)
|
|
725
|
+
}
|
|
715
726
|
}
|
|
716
727
|
function p2u(localPath) {
|
|
717
728
|
/*
|