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 +1 -1
- package/server/index.js +15 -0
- package/server/lib/desktop_event_router.js +143 -35
- package/server/views/terminal.ejs +58 -5
package/package.json
CHANGED
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
|
|
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
|
-
|
|
304
|
-
|
|
322
|
+
const token = value.trim().toLowerCase()
|
|
323
|
+
if (!token || !DESKTOP_EVENT_RUN_TOKEN_PATTERN.test(token)) {
|
|
324
|
+
return null
|
|
305
325
|
}
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
|
320
|
-
const
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
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 (
|
|
1046
|
+
if (typeof sessionValue === "string" && sessionValue.length > 0) {
|
|
1005
1047
|
const baseId = this.baseScriptId || (scriptUri || ("~" + location.pathname))
|
|
1006
|
-
runId = `${baseId}&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
|
-
|
|
1553
|
-
|
|
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 : []
|