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/server/index.js CHANGED
@@ -230,8 +230,40 @@ class Server {
230
230
  }
231
231
  running_dynamic.push(obj)
232
232
  } else {
233
+ const normalizedFilepath = path.normalize(filepath)
234
+ const hasMenuCwd = typeof cwd === 'string'
235
+ const normalizedMenuCwd = hasMenuCwd ? (cwd.length === 0 ? '' : path.normalize(cwd)) : null
236
+ const matchesRunningEntry = (runningKey) => {
237
+ if (typeof runningKey !== 'string' || runningKey.length === 0) {
238
+ return false
239
+ }
240
+ const questionIndex = runningKey.indexOf('?')
241
+ const runningPath = questionIndex >= 0 ? runningKey.slice(0, questionIndex) : runningKey
242
+ if (path.normalize(runningPath) !== normalizedFilepath) {
243
+ return false
244
+ }
245
+ if (!hasMenuCwd) {
246
+ return questionIndex === -1
247
+ }
248
+ if (questionIndex === -1) {
249
+ return normalizedMenuCwd === ''
250
+ }
251
+ const params = querystring.parse(runningKey.slice(questionIndex + 1))
252
+ const rawCwd = typeof params.cwd === 'string' ? params.cwd : null
253
+ if (normalizedMenuCwd === '') {
254
+ return rawCwd !== null && rawCwd.length === 0
255
+ }
256
+ if (!rawCwd || rawCwd.length === 0) {
257
+ return false
258
+ }
259
+ try {
260
+ return path.normalize(rawCwd) === normalizedMenuCwd
261
+ } catch (_) {
262
+ return false
263
+ }
264
+ }
233
265
  for(let running_id in this.kernel.api.running) {
234
- if (running_id.startsWith(id)) {
266
+ if (matchesRunningEntry(running_id)) {
235
267
  let obj2 = structuredClone(obj)
236
268
  obj2.running = true
237
269
  obj2.display = "indent"
@@ -526,9 +558,13 @@ class Server {
526
558
  }
527
559
 
528
560
  let currentBranch = null
561
+ let isDetached = false
529
562
  try {
530
563
  currentBranch = await git.currentBranch({ fs, dir, fullname: false })
531
564
  } catch (_) {}
565
+ if (!currentBranch) {
566
+ isDetached = true
567
+ }
532
568
 
533
569
  let branches = []
534
570
  if (branchList.length > 0) {
@@ -595,6 +631,7 @@ class Server {
595
631
  branch: currentBranch,
596
632
  branches,
597
633
  dir,
634
+ detached: isDetached,
598
635
  logError: logError ? String(logError.message || logError) : null
599
636
  }
600
637
  }
@@ -812,6 +849,7 @@ class Server {
812
849
  let run_tab = "/p/" + name
813
850
  let dev_tab = "/p/" + name + "/dev"
814
851
  let review_tab = "/p/" + name + "/review"
852
+ let files_tab = "/p/" + name + "/files"
815
853
 
816
854
  let editor_tab = `/pinokio/fileview/${encodeURIComponent(name)}`
817
855
  let savedTabs = []
@@ -867,6 +905,7 @@ class Server {
867
905
  run_tab,
868
906
  dev_tab,
869
907
  review_tab,
908
+ files_tab,
870
909
  // paths,
871
910
  theme: this.theme,
872
911
  agent: req.agent,
@@ -1470,10 +1509,15 @@ class Server {
1470
1509
  drive: path.resolve(this.kernel.homedir, "drive"),
1471
1510
  }
1472
1511
  }
1512
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
1513
+ let peer_qr = null
1514
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
1473
1515
  let list = this.getPeers()
1474
1516
  res.render("settings", {
1475
1517
  list,
1476
1518
  current_host: this.kernel.peer.host,
1519
+ peer_url,
1520
+ peer_qr,
1477
1521
  platform,
1478
1522
  version: this.version,
1479
1523
  logo: this.logo,
@@ -2195,6 +2239,10 @@ class Server {
2195
2239
  qr_cloudflare = await QRCode.toDataURL(this.cloudflare_pub)
2196
2240
  }
2197
2241
 
2242
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
2243
+ let peer_qr = null
2244
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
2245
+
2198
2246
  // custom theme
2199
2247
  let exists = await fse.pathExists(this.kernel.path("web"))
2200
2248
  if (exists) {
@@ -2224,6 +2272,8 @@ class Server {
2224
2272
  res.render("index", {
2225
2273
  list,
2226
2274
  current_host: this.kernel.peer.host,
2275
+ peer_url,
2276
+ peer_qr,
2227
2277
  current_urls,
2228
2278
  portal: this.portal,
2229
2279
  install: this.install,
@@ -3885,6 +3935,9 @@ class Server {
3885
3935
  }))
3886
3936
  */
3887
3937
  this.app.get("/tools", ex(async (req, res) => {
3938
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
3939
+ let peer_qr = null
3940
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
3888
3941
  let list = this.getPeers()
3889
3942
  let installs = []
3890
3943
  for(let key in this.kernel.bin.installed) {
@@ -3916,6 +3969,8 @@ class Server {
3916
3969
  }
3917
3970
  res.render("tools", {
3918
3971
  current_host: this.kernel.peer.host,
3972
+ peer_url,
3973
+ peer_qr,
3919
3974
  pending,
3920
3975
  installs,
3921
3976
  bundles,
@@ -4000,9 +4055,14 @@ class Server {
4000
4055
  return (a.name || '').localeCompare(b.name || '')
4001
4056
  })
4002
4057
 
4058
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
4059
+ let peer_qr = null
4060
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
4003
4061
  const list = this.getPeers()
4004
4062
  res.render("terminals", {
4005
4063
  current_host: this.kernel.peer.host,
4064
+ peer_url,
4065
+ peer_qr,
4006
4066
  pluginMenu,
4007
4067
  apps,
4008
4068
  portal: this.portal,
@@ -4016,9 +4076,14 @@ class Server {
4016
4076
  res.redirect(301, "/terminals")
4017
4077
  })
4018
4078
  this.app.get("/screenshots", ex(async (req, res) => {
4079
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
4080
+ let peer_qr = null
4081
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
4019
4082
  let list = this.getPeers()
4020
4083
  res.render("screenshots", {
4021
4084
  current_host: this.kernel.peer.host,
4085
+ peer_url,
4086
+ peer_qr,
4022
4087
  version: this.version,
4023
4088
  portal: this.portal,
4024
4089
  logo: this.logo,
@@ -4105,6 +4170,7 @@ class Server {
4105
4170
  // if <app_name>.localhost
4106
4171
  // otherwise => redirect
4107
4172
 
4173
+ console.log("Chunks", chunks)
4108
4174
 
4109
4175
  if (chunks.length >= 2) {
4110
4176
 
@@ -4151,29 +4217,31 @@ class Server {
4151
4217
  } else {
4152
4218
  nameChunks = chunks
4153
4219
  }
4154
- let name = nameChunks.join(".")
4155
- let api_path = this.kernel.path("api", name)
4156
- let exists = await this.exists(api_path)
4157
- if (exists) {
4158
- let meta = await this.kernel.api.meta(name)
4159
- let launcher = await this.kernel.api.launcher(name)
4160
- let pinokio = launcher.script
4161
- let launchable = false
4162
- if (pinokio && pinokio.menu && pinokio.menu.length > 0) {
4163
- launchable = true
4164
- }
4165
- res.render("start", {
4166
- url,
4167
- launchable,
4168
- autolaunch,
4169
- logo: this.logo,
4170
- theme: this.theme,
4171
- agent: req.agent,
4172
- name: meta.title,
4173
- image: meta.icon,
4174
- link: `/p/${name}?autolaunch=${autolaunch ? "1" : "0"}`,
4175
- })
4176
- return
4220
+ if (nameChunks) {
4221
+ let name = nameChunks.join(".")
4222
+ let api_path = this.kernel.path("api", name)
4223
+ let exists = await this.exists(api_path)
4224
+ if (exists) {
4225
+ let meta = await this.kernel.api.meta(name)
4226
+ let launcher = await this.kernel.api.launcher(name)
4227
+ let pinokio = launcher.script
4228
+ let launchable = false
4229
+ if (pinokio && pinokio.menu && pinokio.menu.length > 0) {
4230
+ launchable = true
4231
+ }
4232
+ res.render("start", {
4233
+ url,
4234
+ launchable,
4235
+ autolaunch,
4236
+ logo: this.logo,
4237
+ theme: this.theme,
4238
+ agent: req.agent,
4239
+ name: meta.title,
4240
+ image: meta.icon,
4241
+ link: `/p/${name}?autolaunch=${autolaunch ? "1" : "0"}`,
4242
+ })
4243
+ return
4244
+ }
4177
4245
  }
4178
4246
  }
4179
4247
  res.render("start", {
@@ -4286,9 +4354,14 @@ class Server {
4286
4354
  drive: path.resolve(this.kernel.homedir, "drive"),
4287
4355
  }
4288
4356
  }
4357
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
4358
+ let peer_qr = null
4359
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
4289
4360
  let list = this.getPeers()
4290
4361
  res.render("settings", {
4291
4362
  current_host: this.kernel.peer.host,
4363
+ peer_url,
4364
+ peer_qr,
4292
4365
  list,
4293
4366
  platform,
4294
4367
  version: this.version,
@@ -4404,6 +4477,9 @@ class Server {
4404
4477
  return
4405
4478
  }
4406
4479
 
4480
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
4481
+ let peer_qr = null
4482
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
4407
4483
  let list = this.getPeers()
4408
4484
  let ai = await this.kernel.proto.ai()
4409
4485
  ai = [{
@@ -4417,6 +4493,8 @@ class Server {
4417
4493
  list,
4418
4494
  ai,
4419
4495
  current_host: this.kernel.peer.host,
4496
+ peer_url,
4497
+ peer_qr,
4420
4498
  cwd: this.kernel.path("api"),
4421
4499
  name: null,
4422
4500
  // name: req.params.name,
@@ -4517,9 +4595,14 @@ class Server {
4517
4595
  } catch (e) {
4518
4596
  }
4519
4597
  }
4598
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
4599
+ let peer_qr = null
4600
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
4520
4601
  res.render(`connect`, {
4521
4602
  current_urls,
4522
4603
  current_host: this.kernel.peer.host,
4604
+ peer_url,
4605
+ peer_qr,
4523
4606
  list,
4524
4607
  portal: this.portal,
4525
4608
  logo: this.logo,
@@ -4661,6 +4744,13 @@ class Server {
4661
4744
  this.app.post("/push", ex(async (req, res) => {
4662
4745
  try {
4663
4746
  const payload = { ...(req.body || {}) }
4747
+ // Normalise audience and device targeting
4748
+ if (typeof payload.audience === 'string') {
4749
+ payload.audience = payload.audience.trim() || undefined
4750
+ }
4751
+ if (typeof payload.device_id === 'string') {
4752
+ payload.device_id = payload.device_id.trim() || undefined
4753
+ }
4664
4754
  const resolveAssetPath = (raw) => {
4665
4755
  if (typeof raw !== 'string') {
4666
4756
  return null
@@ -4743,6 +4833,19 @@ class Server {
4743
4833
  }
4744
4834
  delete payload.soundUrl
4745
4835
  delete payload.soundPath
4836
+ // For device-scoped notifications, suppress host OS notifier for remote origins,
4837
+ // but allow it when the request originates from the local machine
4838
+ if (payload.audience === 'device' && typeof payload.device_id === 'string' && payload.device_id) {
4839
+ try {
4840
+ if (this.socket && typeof this.socket.isLocalDevice === 'function') {
4841
+ payload.host = !!this.socket.isLocalDevice(payload.device_id)
4842
+ } else {
4843
+ payload.host = false
4844
+ }
4845
+ } catch (_) {
4846
+ payload.host = false
4847
+ }
4848
+ }
4746
4849
  Util.push(payload)
4747
4850
  res.json({ success: true })
4748
4851
  } catch (e) {
@@ -5304,6 +5407,9 @@ class Server {
5304
5407
  let static_routes = Object.keys(this.kernel.router.rewrite_mapping).map((key) => {
5305
5408
  return this.kernel.router.rewrite_mapping[key]
5306
5409
  })
5410
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
5411
+ let peer_qr = null
5412
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
5307
5413
  res.render("net", {
5308
5414
  static_routes,
5309
5415
  selected_name: req.params.name,
@@ -5322,6 +5428,8 @@ class Server {
5322
5428
  peer,
5323
5429
  protocol,
5324
5430
  current_host: this.kernel.peer.host,
5431
+ peer_url,
5432
+ peer_qr,
5325
5433
  })
5326
5434
  }))
5327
5435
  this.app.get("/network", ex(async (req, res) => {
@@ -5463,6 +5571,9 @@ class Server {
5463
5571
  let static_routes = Object.keys(this.kernel.router.rewrite_mapping).map((key) => {
5464
5572
  return this.kernel.router.rewrite_mapping[key]
5465
5573
  })
5574
+ const peer_url = `http://${this.kernel.peer.host}:${DEFAULT_PORT}`
5575
+ let peer_qr = null
5576
+ try { peer_qr = await QRCode.toDataURL(peer_url) } catch (_) {}
5466
5577
  res.render("network", {
5467
5578
  static_routes,
5468
5579
  host,
@@ -5488,6 +5599,8 @@ class Server {
5488
5599
  peer_active: this.kernel.peer.active,
5489
5600
  port_mapping: this.kernel.router.port_mapping,
5490
5601
  // port_mapping: this.kernel.caddy.port_mapping,
5602
+ peer_url,
5603
+ peer_qr,
5491
5604
  // ip_mapping: this.kernel.caddy.ip_mapping,
5492
5605
  lan: this.kernel.router.local_network_mapping,
5493
5606
  agent: req.agent,
@@ -6246,8 +6359,84 @@ class Server {
6246
6359
  res.json(response)
6247
6360
  }))
6248
6361
  this.app.get("/info/git/:ref/*", ex(async (req, res) => {
6249
- let response = await this.getGit(req.params.ref, req.params[0])
6250
- res.json(response)
6362
+ const repoParam = req.params[0]
6363
+ const ref = req.params.ref || 'HEAD'
6364
+ const summary = await this.getGit(ref, repoParam)
6365
+
6366
+ const repoDir = summary && summary.dir ? summary.dir : this.kernel.path('api', repoParam)
6367
+
6368
+ if (ref === 'HEAD') {
6369
+ try {
6370
+ const { changes: headChanges, git_commit_url } = await this.getRepoHeadStatus(repoParam)
6371
+ summary.changes = headChanges || []
6372
+ summary.git_commit_url = git_commit_url || null
6373
+ } catch (error) {
6374
+ console.error('[git-info] head status error', repoParam, error)
6375
+ summary.changes = []
6376
+ }
6377
+ } else {
6378
+ let changes = []
6379
+ try {
6380
+ const commitOid = await this.kernel.git.resolveCommitOid(repoDir, ref)
6381
+ const parentOid = await this.kernel.git.getParentCommit(repoDir, commitOid)
6382
+ let entries
6383
+ if (parentOid !== commitOid) {
6384
+ entries = await git.walk({
6385
+ fs,
6386
+ dir: repoDir,
6387
+ trees: [git.TREE({ ref: parentOid }), git.TREE({ ref: commitOid })],
6388
+ map: async (filepath, [A, B]) => {
6389
+ if (filepath === '.') return
6390
+ if (!A && B) return { filepath, type: 'added' }
6391
+ if (A && !B) return { filepath, type: 'deleted' }
6392
+ if (A && B) {
6393
+ const Aoid = await A.oid()
6394
+ const Boid = await B.oid()
6395
+ if (Aoid !== Boid) return { filepath, type: 'modified' }
6396
+ }
6397
+ },
6398
+ })
6399
+ } else {
6400
+ entries = await git.walk({
6401
+ fs,
6402
+ dir: repoDir,
6403
+ trees: [git.TREE({ ref: commitOid })],
6404
+ map: async (filepath, [B]) => {
6405
+ if (filepath === '.') return
6406
+ return { filepath, type: 'added' }
6407
+ },
6408
+ })
6409
+ }
6410
+ const diffFiles = (entries || []).filter(Boolean)
6411
+ for (const { filepath, type } of diffFiles) {
6412
+ const fullPath = path.join(repoDir, filepath)
6413
+ const stats = await fs.promises.stat(fullPath).catch(() => null)
6414
+ if (!stats || stats.isDirectory()) {
6415
+ continue
6416
+ }
6417
+ const relpath = path.relative(this.kernel.path('api'), fullPath)
6418
+ changes.push({
6419
+ ref,
6420
+ webpath: "/asset/" + path.relative(this.kernel.homedir, fullPath),
6421
+ file: filepath,
6422
+ path: fullPath,
6423
+ diffpath: `/gitdiff/${ref}/${repoParam}/${filepath}`,
6424
+ status: type,
6425
+ relpath,
6426
+ })
6427
+ }
6428
+ } catch (error) {
6429
+ console.error('[git-info] diff error', repoParam, ref, error)
6430
+ }
6431
+ summary.changes = changes
6432
+ }
6433
+
6434
+ if (!summary.git_commit_url) {
6435
+ summary.git_commit_url = `/run/scripts/git/commit.json?cwd=${repoDir}&callback_target=parent&callback=$location.href`
6436
+ }
6437
+ summary.dir = repoDir
6438
+
6439
+ res.json(summary)
6251
6440
  }))
6252
6441
  this.app.get("/info/gitstatus/:name", ex(async (req, res) => {
6253
6442
  try {
@@ -6944,6 +7133,35 @@ class Server {
6944
7133
  let current_peer_info = await this.kernel.peer.current_host()
6945
7134
  res.json(current_peer_info)
6946
7135
  }))
7136
+ this.app.get("/info/router", ex(async (req, res) => {
7137
+ try {
7138
+ // Lightweight router mapping without favicon or installed scans
7139
+ const https_active = this.kernel.peer.https_active
7140
+ const router_info = await this.kernel.peer.router_info_lite()
7141
+ const rewrite_mapping = this.kernel.router.rewrite_mapping
7142
+ const router = this.kernel.router.published()
7143
+ res.json({ https_active, router_info, rewrite_mapping, router })
7144
+ } catch (err) {
7145
+ res.json({ https_active: false, router_info: [], rewrite_mapping: {}, router: {} })
7146
+ }
7147
+ }))
7148
+ this.app.get("/qr", ex(async (req, res) => {
7149
+ try {
7150
+ const data = typeof req.query.data === 'string' ? req.query.data : ''
7151
+ if (!data) {
7152
+ res.status(400).json({ error: 'Missing data parameter' })
7153
+ return
7154
+ }
7155
+ const scale = Math.max(2, Math.min(10, parseInt(req.query.s || '4', 10) || 4))
7156
+ const margin = Math.max(0, Math.min(4, parseInt(req.query.m || '0', 10) || 0))
7157
+ const buf = await QRCode.toBuffer(data, { type: 'png', scale, margin })
7158
+ res.setHeader('Content-Type', 'image/png')
7159
+ res.setHeader('Cache-Control', 'no-store')
7160
+ res.send(buf)
7161
+ } catch (err) {
7162
+ res.status(500).json({ error: 'Failed to generate QR' })
7163
+ }
7164
+ }))
6947
7165
  this.app.get("/info/api", ex(async (req,res) => {
6948
7166
  // api related info
6949
7167
  let repo = this.kernel.git.find(req.query.git)
@@ -7052,10 +7270,12 @@ class Server {
7052
7270
  let run_tab = "/p/" + name
7053
7271
  let dev_tab = "/p/" + name + "/dev"
7054
7272
  let review_tab = "/p/" + name + "/review"
7273
+ let files_tab = "/p/" + name + "/files"
7055
7274
  res.render("review", {
7056
7275
  run_tab,
7057
7276
  dev_tab,
7058
7277
  review_tab,
7278
+ files_tab,
7059
7279
  name: req.params.name,
7060
7280
  type: "review",
7061
7281
  title: name,
@@ -7070,6 +7290,9 @@ class Server {
7070
7290
  this.app.get("/p/:name/dev", ex(async (req, res) => {
7071
7291
  await this.chrome(req, res, "browse")
7072
7292
  }))
7293
+ this.app.get("/p/:name/files", ex(async (req, res) => {
7294
+ await this.chrome(req, res, "files")
7295
+ }))
7073
7296
  this.app.get("/p/:name/browse", ex(async (req, res) => {
7074
7297
  await this.chrome(req, res, "browse")
7075
7298
  }))