pinokiod 6.0.55 → 6.0.56

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "6.0.55",
3
+ "version": "6.0.56",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -10870,6 +10870,21 @@ class Server {
10870
10870
  })
10871
10871
  this.app.post("/pinokio/event", handlePinokioEvent)
10872
10872
  this.app.post("/pinokio/desktop/event", handlePinokioEvent)
10873
+ this.app.get("/pinokio/event/run/:token", ex(async (req, res) => {
10874
+ const token = req && req.params && typeof req.params.token === "string" ? req.params.token : ""
10875
+ const payload = this.desktopEventRouter.resolveRun(token)
10876
+ if (!payload) {
10877
+ res.status(404).json({
10878
+ ok: false,
10879
+ error: "Event run payload not found"
10880
+ })
10881
+ return
10882
+ }
10883
+ res.json({
10884
+ ok: true,
10885
+ ...payload
10886
+ })
10887
+ }))
10873
10888
  this.app.post("/pinokio/peer/announce_kill", ex(async (req, res) => {
10874
10889
  this.kernel.peer.kill(req.body.host)
10875
10890
  }))
@@ -1,9 +1,13 @@
1
1
  "use strict"
2
2
 
3
+ const crypto = require("crypto")
3
4
  const fs = require("fs")
4
5
  const path = require("path")
5
6
 
6
7
  const DESKTOP_EVENT_NAME_PATTERN = /^[a-z0-9][a-z0-9:_-]{0,127}$/
8
+ const DESKTOP_EVENT_RUN_TOKEN_PATTERN = /^[a-f0-9]{32}$/i
9
+ const DESKTOP_EVENT_RUN_TTL_MS = 60 * 60 * 1000
10
+ const DESKTOP_EVENT_RUN_STORE_LIMIT = 2048
7
11
  const WORKSPACE_PATH_PATTERNS = [
8
12
  /^\/pinokio\/([^/?#]+)/i,
9
13
  /^\/p\/([^/?#]+)/i,
@@ -296,50 +300,135 @@ const normalizeDesktopEventUi = (ui = {}) => {
296
300
  return normalized
297
301
  }
298
302
 
299
- const serializeDesktopEventValue = (value) => {
300
- if (value === undefined) {
303
+ const cloneDesktopEventValue = (value) => {
304
+ if (value === null || value === undefined) {
305
+ return value
306
+ }
307
+ try {
308
+ return structuredClone(value)
309
+ } catch (_) {
310
+ try {
311
+ return JSON.parse(JSON.stringify(value))
312
+ } catch (_) {
313
+ return value
314
+ }
315
+ }
316
+ }
317
+
318
+ const normalizeDesktopEventRunToken = (value) => {
319
+ if (typeof value !== "string") {
301
320
  return null
302
321
  }
303
- if (value === null) {
304
- return ""
322
+ const token = value.trim().toLowerCase()
323
+ if (!token || !DESKTOP_EVENT_RUN_TOKEN_PATTERN.test(token)) {
324
+ return null
305
325
  }
306
- if (typeof value === "string") {
307
- return value
326
+ return token
327
+ }
328
+
329
+ const createDesktopEventRunStore = () => {
330
+ const runs = new Map()
331
+
332
+ const pruneExpired = () => {
333
+ const now = Date.now()
334
+ for (const [token, entry] of runs.entries()) {
335
+ if (!entry || !entry.expiresAt || entry.expiresAt <= now) {
336
+ runs.delete(token)
337
+ }
338
+ }
308
339
  }
309
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
310
- return String(value)
340
+
341
+ const trimToLimit = () => {
342
+ if (runs.size < DESKTOP_EVENT_RUN_STORE_LIMIT) {
343
+ return
344
+ }
345
+ const entries = [...runs.entries()].sort((a, b) => {
346
+ const aTime = a[1] && a[1].createdAt ? a[1].createdAt : 0
347
+ const bTime = b[1] && b[1].createdAt ? b[1].createdAt : 0
348
+ return aTime - bTime
349
+ })
350
+ const overflow = (runs.size - DESKTOP_EVENT_RUN_STORE_LIMIT) + 1
351
+ for (let i = 0; i < overflow; i += 1) {
352
+ const entry = entries[i]
353
+ if (entry && entry[0]) {
354
+ runs.delete(entry[0])
355
+ }
356
+ }
311
357
  }
312
- try {
313
- return JSON.stringify(value)
314
- } catch (_) {
315
- return String(value)
358
+
359
+ const save = (payload = {}) => {
360
+ pruneExpired()
361
+ trimToLimit()
362
+ const token = crypto.randomBytes(16).toString("hex")
363
+ const now = Date.now()
364
+ runs.set(token, {
365
+ createdAt: now,
366
+ expiresAt: now + DESKTOP_EVENT_RUN_TTL_MS,
367
+ payload: cloneDesktopEventValue(payload)
368
+ })
369
+ return token
370
+ }
371
+
372
+ const read = (token) => {
373
+ pruneExpired()
374
+ const normalized = normalizeDesktopEventRunToken(token)
375
+ if (!normalized) {
376
+ return null
377
+ }
378
+ const entry = runs.get(normalized)
379
+ if (!entry) {
380
+ return null
381
+ }
382
+ if (!entry.expiresAt || entry.expiresAt <= Date.now()) {
383
+ runs.delete(normalized)
384
+ return null
385
+ }
386
+ return cloneDesktopEventValue(entry.payload)
387
+ }
388
+
389
+ return {
390
+ save,
391
+ read
316
392
  }
317
393
  }
318
394
 
319
- const buildDesktopEventQueryParams = (eventName, payload) => {
320
- const params = new URLSearchParams()
321
- if (payload && typeof payload === "object" && !Array.isArray(payload)) {
395
+ const normalizeDesktopEventInput = ({ eventName, payload, context = {} }) => {
396
+ const input = {}
397
+ const positional = []
398
+
399
+ if (Array.isArray(payload)) {
400
+ positional.push(...payload)
401
+ } else if (payload && typeof payload === "object") {
402
+ if (Array.isArray(payload._)) {
403
+ positional.push(...payload._)
404
+ } else if (Array.isArray(payload.args)) {
405
+ positional.push(...payload.args)
406
+ }
322
407
  for (const [key, value] of Object.entries(payload)) {
323
- if (!key || key.startsWith("__")) {
408
+ if (!key || key === "_" || key === "args" || key.startsWith("__")) {
324
409
  continue
325
410
  }
326
- const serialized = serializeDesktopEventValue(value)
327
- if (serialized !== null) {
328
- params.set(key, serialized)
329
- }
330
- }
331
- } else if (payload !== undefined) {
332
- const serialized = serializeDesktopEventValue(payload)
333
- if (serialized !== null) {
334
- params.set("value", serialized)
411
+ input[key] = cloneDesktopEventValue(value)
335
412
  }
413
+ } else if (payload !== undefined && payload !== null) {
414
+ positional.push(payload)
336
415
  }
337
- params.set("__desktop_event", eventName)
338
- params.set("__desktop_payload", serializeDesktopEventValue(payload === undefined ? {} : payload) || "{}")
339
- return params
416
+
417
+ input._ = positional.map((value) => cloneDesktopEventValue(value))
418
+ input.event = {
419
+ name: eventName,
420
+ source: typeof context.source === "string" ? context.source : "",
421
+ sourceEvent: typeof context.sourceEvent === "string" ? context.sourceEvent : "",
422
+ pageUrl: typeof context.pageUrl === "string" ? context.pageUrl : "",
423
+ frameUrl: typeof context.frameUrl === "string" ? context.frameUrl : "",
424
+ currentUrl: typeof context.currentUrl === "string" ? context.currentUrl : "",
425
+ topUrl: typeof context.topUrl === "string" ? context.topUrl : "",
426
+ referrerUrl: typeof context.referrerUrl === "string" ? context.referrerUrl : ""
427
+ }
428
+ return input
340
429
  }
341
430
 
342
- const resolveDesktopEventHandler = async ({ kernel, eventName, payload, context = {} }) => {
431
+ const resolveDesktopEventHandler = async ({ kernel, eventName, payload, context = {}, runStore }) => {
343
432
  const workspace = await resolveDesktopEventWorkspace(context, kernel)
344
433
  if (!workspace) {
345
434
  return { matched: false, reason: "workspace_not_resolved" }
@@ -433,10 +522,18 @@ const resolveDesktopEventHandler = async ({ kernel, eventName, payload, context
433
522
  }
434
523
 
435
524
  const queryParams = new URLSearchParams(querySuffix)
436
- const payloadParams = buildDesktopEventQueryParams(eventName, payload)
437
- for (const [key, value] of payloadParams.entries()) {
438
- queryParams.set(key, value)
439
- }
525
+ const runInput = normalizeDesktopEventInput({ eventName, payload, context })
526
+ const runToken = runStore && typeof runStore.save === "function"
527
+ ? runStore.save({
528
+ workspace,
529
+ event: eventName,
530
+ input: runInput
531
+ })
532
+ : null
533
+ if (!runToken) {
534
+ return { matched: false, workspace, reason: "event_run_token_failed" }
535
+ }
536
+ queryParams.set("__pinokio_event_run", runToken)
440
537
 
441
538
  const queryString = queryParams.toString()
442
539
  const launchUrl = queryString ? `${launchPath}?${queryString}` : launchPath
@@ -452,6 +549,8 @@ const resolveDesktopEventHandler = async ({ kernel, eventName, payload, context
452
549
  }
453
550
 
454
551
  const createDesktopEventRouter = ({ kernel }) => {
552
+ const runStore = createDesktopEventRunStore()
553
+
455
554
  const handle = async (body = {}) => {
456
555
  const eventName = normalizeDesktopEventName(body ? body.event : null)
457
556
  if (!eventName) {
@@ -465,7 +564,7 @@ const createDesktopEventRouter = ({ kernel }) => {
465
564
  }
466
565
  const payload = body && Object.prototype.hasOwnProperty.call(body, "payload") ? body.payload : {}
467
566
  const context = body && body.context && typeof body.context === "object" ? body.context : {}
468
- const result = await resolveDesktopEventHandler({ kernel, eventName, payload, context })
567
+ const result = await resolveDesktopEventHandler({ kernel, eventName, payload, context, runStore })
469
568
  if (!result.matched) {
470
569
  return {
471
570
  status: 200,
@@ -490,8 +589,17 @@ const createDesktopEventRouter = ({ kernel }) => {
490
589
  }
491
590
  }
492
591
 
592
+ const resolveRun = (token) => {
593
+ const payload = runStore.read(token)
594
+ if (!payload || typeof payload !== "object") {
595
+ return null
596
+ }
597
+ return payload
598
+ }
599
+
493
600
  return {
494
- handle
601
+ handle,
602
+ resolveRun
495
603
  }
496
604
  }
497
605
 
@@ -999,11 +999,53 @@ document.addEventListener("DOMContentLoaded", async () => {
999
999
  this.socket.close()
1000
1000
 
1001
1001
  const searchParams = new URLSearchParams(location.search)
1002
- const query = Object.fromEntries(searchParams)
1002
+ let query = {}
1003
+ const positionalArgs = searchParams.getAll("_")
1004
+ if (positionalArgs.length > 0) {
1005
+ query._ = positionalArgs
1006
+ }
1007
+ for (const [key, value] of searchParams.entries()) {
1008
+ if (!key || key === "_" || key === "__pinokio_event_panel" || key === "__pinokio_event_run") {
1009
+ continue
1010
+ }
1011
+ if (Object.prototype.hasOwnProperty.call(query, key)) {
1012
+ if (Array.isArray(query[key])) {
1013
+ query[key].push(value)
1014
+ } else {
1015
+ query[key] = [query[key], value]
1016
+ }
1017
+ } else {
1018
+ query[key] = value
1019
+ }
1020
+ }
1021
+
1022
+ const eventRunToken = searchParams.get("__pinokio_event_run")
1023
+ if (eventRunToken) {
1024
+ const response = await fetch(`/pinokio/event/run/${encodeURIComponent(eventRunToken)}`, {
1025
+ method: "GET",
1026
+ headers: {
1027
+ "Accept": "application/json"
1028
+ }
1029
+ })
1030
+ if (!response.ok) {
1031
+ throw new Error("Event payload not found or expired")
1032
+ }
1033
+ const runPayload = await response.json().catch(() => null)
1034
+ const eventInput = runPayload && runPayload.input && typeof runPayload.input === "object"
1035
+ ? runPayload.input
1036
+ : {}
1037
+ const merged = Object.assign({}, query, eventInput)
1038
+ merged._ = Array.isArray(eventInput._)
1039
+ ? eventInput._
1040
+ : (Array.isArray(query._) ? query._ : [])
1041
+ query = merged
1042
+ }
1043
+
1044
+ const sessionValue = Array.isArray(query.session) ? query.session[0] : query.session
1003
1045
  let runId = this.baseScriptId
1004
- if (query.session) {
1046
+ if (typeof sessionValue === "string" && sessionValue.length > 0) {
1005
1047
  const baseId = this.baseScriptId || (scriptUri || ("~" + location.pathname))
1006
- runId = `${baseId}&session=${query.session}`
1048
+ runId = `${baseId}&session=${sessionValue}`
1007
1049
  }
1008
1050
  this.currentId = runId || null
1009
1051
 
@@ -1549,8 +1591,19 @@ document.addEventListener("DOMContentLoaded", async () => {
1549
1591
  <% } else { %>
1550
1592
  await this.createTerm(xtermTheme.Tomorrow)
1551
1593
  <% } %>
1552
- await this.start(mode)
1553
- return true
1594
+ try {
1595
+ await this.start(mode)
1596
+ return true
1597
+ } catch (error) {
1598
+ runControls.set("idle")
1599
+ const message = error && error.message ? error.message : "Failed to start"
1600
+ n.Noty({
1601
+ text: message,
1602
+ type: "error",
1603
+ timeout: 4000
1604
+ })
1605
+ return false
1606
+ }
1554
1607
  }
1555
1608
  async uploadFiles(files, overlay) {
1556
1609
  const localFiles = Array.isArray(files) ? files : []