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 CHANGED
@@ -1,55 +1,112 @@
1
1
  const axios = require('axios')
2
- const { URL } = require("url");
3
- const { JSDOM } = require("jsdom");
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("Invalid URL: " + pageUrl);
40
+ throw new Error('Invalid URL: ' + pageUrl)
12
41
  }
13
42
 
14
- const candidates = [
15
- "/favicon.ico",
16
- "/favicon.png",
17
- "/assets/favicon.ico",
18
- "/static/favicon.ico",
19
- "/favicon.svg",
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
- // 2. Fallback to parsing HTML
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 res = await axios.get(pageUrl, { timeout: 1000 });
31
- const dom = new JSDOM(res.data);
32
- const icons = Array.from(dom.window.document.querySelectorAll("link[rel~='icon'], link[rel='apple-touch-icon']"));
33
-
34
- for (const icon of icons) {
35
- const href = icon.getAttribute("href");
36
- if (!href) continue;
37
- const resolvedUrl = new URL(href, origin).href;
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
- // Ignore HTML errors
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
- return null;
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: 3000 });
50
- return res.status === 200 && res.headers["content-type"]?.startsWith("image/");
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
- notifier.notify(notifyParams)
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
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "3.170.0",
3
+ "version": "3.181.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {