pinokiod 6.0.46 → 6.0.48

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.46",
3
+ "version": "6.0.48",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,6 +5,7 @@ const path = require("path")
5
5
 
6
6
  const DESKTOP_EVENT_NAME_PATTERN = /^[a-z0-9][a-z0-9:_-]{0,127}$/
7
7
  const WORKSPACE_PATH_PATTERNS = [
8
+ /^\/pinokio\/([^/?#]+)/i,
8
9
  /^\/p\/([^/?#]+)/i,
9
10
  /^\/api\/([^/?#]+)/i,
10
11
  /^\/_api\/([^/?#]+)/i,
@@ -100,7 +101,134 @@ const extractWorkspaceFromUrl = (rawUrl) => {
100
101
  return extractWorkspaceFromPathname(parsedUrl.pathname)
101
102
  }
102
103
 
103
- const resolveDesktopEventWorkspace = async (context = {}) => {
104
+ const toHttpHostKey = (rawUrl) => {
105
+ if (typeof rawUrl !== "string") {
106
+ return null
107
+ }
108
+ const value = rawUrl.trim()
109
+ if (!value) {
110
+ return null
111
+ }
112
+ let parsed
113
+ try {
114
+ parsed = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value)
115
+ ? new URL(value)
116
+ : new URL(value, "http://localhost")
117
+ } catch (_) {
118
+ return null
119
+ }
120
+ const protocol = parsed.protocol || ""
121
+ if (protocol !== "http:" && protocol !== "https:") {
122
+ return null
123
+ }
124
+ const host = (parsed.host || "").trim().toLowerCase()
125
+ return host || null
126
+ }
127
+
128
+ const collectUrlLikeValues = (value, out = new Set(), depth = 0) => {
129
+ if (depth > 4 || value === null || value === undefined) {
130
+ return out
131
+ }
132
+ if (typeof value === "string") {
133
+ const trimmed = value.trim()
134
+ if (trimmed) {
135
+ out.add(trimmed)
136
+ }
137
+ return out
138
+ }
139
+ if (Array.isArray(value)) {
140
+ for (const entry of value) {
141
+ collectUrlLikeValues(entry, out, depth + 1)
142
+ }
143
+ return out
144
+ }
145
+ if (typeof value === "object") {
146
+ for (const [key, entry] of Object.entries(value)) {
147
+ if (!key) {
148
+ continue
149
+ }
150
+ if (typeof entry === "string" && /url/i.test(key)) {
151
+ const trimmed = entry.trim()
152
+ if (trimmed) {
153
+ out.add(trimmed)
154
+ }
155
+ continue
156
+ }
157
+ if (typeof entry === "object" && entry !== null) {
158
+ collectUrlLikeValues(entry, out, depth + 1)
159
+ }
160
+ }
161
+ }
162
+ return out
163
+ }
164
+
165
+ const resolveWorkspaceFromRunningScripts = (kernel, context = {}) => {
166
+ if (!kernel || !kernel.info || typeof kernel.info.scriptsByApi !== "function") {
167
+ return null
168
+ }
169
+ const contextHosts = new Set()
170
+ const contextUrlCandidates = [
171
+ context.frameUrl,
172
+ context.pageUrl,
173
+ context.currentUrl,
174
+ context.sourceUrl,
175
+ context.referrerUrl,
176
+ context.topUrl,
177
+ context.destinationUrl,
178
+ context.url,
179
+ ]
180
+ for (const candidate of contextUrlCandidates) {
181
+ const hostKey = toHttpHostKey(candidate)
182
+ if (hostKey) {
183
+ contextHosts.add(hostKey)
184
+ }
185
+ }
186
+ if (contextHosts.size === 0) {
187
+ return null
188
+ }
189
+ let scriptsByApi
190
+ try {
191
+ scriptsByApi = kernel.info.scriptsByApi()
192
+ } catch (_) {
193
+ return null
194
+ }
195
+ if (!scriptsByApi || typeof scriptsByApi !== "object") {
196
+ return null
197
+ }
198
+ const matches = new Set()
199
+ for (const [workspace, entries] of Object.entries(scriptsByApi)) {
200
+ if (!workspace || !Array.isArray(entries)) {
201
+ continue
202
+ }
203
+ const normalizedWorkspace = sanitizeDesktopWorkspaceName(workspace)
204
+ if (!normalizedWorkspace) {
205
+ continue
206
+ }
207
+ let matched = false
208
+ for (const entry of entries) {
209
+ const urlValues = collectUrlLikeValues(entry, new Set())
210
+ for (const value of urlValues) {
211
+ const hostKey = toHttpHostKey(value)
212
+ if (hostKey && contextHosts.has(hostKey)) {
213
+ matched = true
214
+ break
215
+ }
216
+ }
217
+ if (matched) {
218
+ break
219
+ }
220
+ }
221
+ if (matched) {
222
+ matches.add(normalizedWorkspace)
223
+ }
224
+ }
225
+ if (matches.size === 1) {
226
+ return [...matches][0]
227
+ }
228
+ return null
229
+ }
230
+
231
+ const resolveDesktopEventWorkspace = async (context = {}, kernel = null) => {
104
232
  const explicitCandidates = [
105
233
  context.workspace,
106
234
  context.app,
@@ -114,11 +242,14 @@ const resolveDesktopEventWorkspace = async (context = {}) => {
114
242
  }
115
243
  }
116
244
  const urlCandidates = [
245
+ context.frameUrl,
117
246
  context.pageUrl,
118
247
  context.url,
119
248
  context.currentUrl,
120
249
  context.sourceUrl,
121
250
  context.referrerUrl,
251
+ context.topUrl,
252
+ context.destinationUrl,
122
253
  ]
123
254
  for (const candidate of urlCandidates) {
124
255
  const workspaceName = extractWorkspaceFromUrl(candidate)
@@ -126,6 +257,10 @@ const resolveDesktopEventWorkspace = async (context = {}) => {
126
257
  return workspaceName
127
258
  }
128
259
  }
260
+ const workspaceByHost = resolveWorkspaceFromRunningScripts(kernel, context)
261
+ if (workspaceByHost) {
262
+ return workspaceByHost
263
+ }
129
264
  return null
130
265
  }
131
266
 
@@ -195,7 +330,7 @@ const buildDesktopEventQueryParams = (eventName, payload) => {
195
330
  }
196
331
 
197
332
  const resolveDesktopEventHandler = async ({ kernel, eventName, payload, context = {} }) => {
198
- const workspace = await resolveDesktopEventWorkspace(context)
333
+ const workspace = await resolveDesktopEventWorkspace(context, kernel)
199
334
  if (!workspace) {
200
335
  return { matched: false, reason: "workspace_not_resolved" }
201
336
  }
@@ -479,6 +479,27 @@
479
479
  });
480
480
  }
481
481
 
482
+ function relayDesktopEventLaunch(payload, excludedSource = null) {
483
+ if (!payload || typeof payload !== 'object' || payload.e !== 'pinokio:desktop-event-launch') {
484
+ return 0;
485
+ }
486
+ let relayed = 0;
487
+ leafElements.forEach((entry) => {
488
+ const targetWindow = entry && entry.iframe ? entry.iframe.contentWindow : null;
489
+ if (!targetWindow) {
490
+ return;
491
+ }
492
+ if (excludedSource && targetWindow === excludedSource) {
493
+ return;
494
+ }
495
+ try {
496
+ targetWindow.postMessage(payload, '*');
497
+ relayed += 1;
498
+ } catch (_) {}
499
+ });
500
+ return relayed;
501
+ }
502
+
482
503
  let activeResize = null;
483
504
 
484
505
  function beginResize(splitId, pointerEvent) {
@@ -665,6 +686,10 @@
665
686
  if (!event || !event.data || typeof event.data !== 'object') {
666
687
  return;
667
688
  }
689
+ if (event.data.e === 'pinokio:desktop-event-launch') {
690
+ relayDesktopEventLaunch(event.data, event.source || null);
691
+ return;
692
+ }
668
693
  if (event.data.e === 'layout-state-request') {
669
694
  let frameEntry = null;
670
695
  let frameId = null;
@@ -770,6 +795,15 @@
770
795
 
771
796
  initLayout();
772
797
  window.addEventListener('message', onMessage);
798
+ window.addEventListener('pinokio:desktop-event-launch', (event) => {
799
+ const detail = event && event.detail && typeof event.detail === 'object'
800
+ ? event.detail
801
+ : null;
802
+ if (!detail || detail.e !== 'pinokio:desktop-event-launch') {
803
+ return;
804
+ }
805
+ relayDesktopEventLaunch(detail, null);
806
+ });
773
807
  window.addEventListener('resize', onResize);
774
808
  window.addEventListener('pinokio:viewport-change', onResize);
775
809
 
@@ -7422,6 +7422,12 @@ const rerenderMenuSection = (container, html) => {
7422
7422
  //if (String(port) === "<%=port%>" || /https:\/\/pinokio\..*localhost/.test(origin)) {
7423
7423
  //if (String(port) === "<%=port%>") {
7424
7424
  if (event.data) {
7425
+ if (event.data.e === "pinokio:desktop-event-launch") {
7426
+ return
7427
+ }
7428
+ if (event.data.e === "pinokio:desktop-event") {
7429
+ return
7430
+ }
7425
7431
  if (event.data.action) {
7426
7432
  if (event.data.action.type === "newtab") {
7427
7433
  addTab(event.data.action.url)
@@ -7437,18 +7443,23 @@ const rerenderMenuSection = (container, html) => {
7437
7443
  }
7438
7444
  }
7439
7445
  } else if (event.data.launch) {
7440
- const rawName = event.data.launch.name
7446
+ const launchPayload = event.data.launch
7447
+ const rawName = launchPayload && typeof launchPayload.name === "string" ? launchPayload.name : ""
7448
+ const launchHref = launchPayload && typeof launchPayload.href === "string" ? launchPayload.href : ""
7449
+ if (!rawName || !launchHref) {
7450
+ return
7451
+ }
7441
7452
  const escapedSelector = escapeTargetSelector(rawName)
7442
7453
  if (escapedSelector) {
7443
7454
  global_selector = `.frame-link[target="${escapedSelector}"]`
7444
7455
  } else {
7445
7456
  global_selector = null
7446
7457
  }
7447
- const frameExists = Array.from(document.querySelectorAll("main.browserview iframe")).some((frame) => {
7458
+ const frameExists = Array.from(document.querySelectorAll("main.browserview iframe")).some((frame) => {
7448
7459
  return frame.name === rawName
7449
7460
  })
7450
7461
  if (!frameExists) {
7451
- create_iframe(rawName, event.data.launch.href)
7462
+ create_iframe(rawName, launchHref)
7452
7463
  }
7453
7464
  refresh()
7454
7465
  } else if (event.data.type === "shell-session-id") {
@@ -11876,6 +11887,19 @@ document.addEventListener("DOMContentLoaded", () => {
11876
11887
  }
11877
11888
  })
11878
11889
 
11890
+ const openDesktopEvent = (launch) => {
11891
+ if (!launch || typeof launch !== "object") {
11892
+ return false
11893
+ }
11894
+ const rawUrl = typeof launch.url === "string" ? launch.url : ""
11895
+ if (!rawUrl) {
11896
+ return false
11897
+ }
11898
+ return openWithUrl(rawUrl, {
11899
+ workspaceCwd: resolveWorkspaceCwd(launch)
11900
+ })
11901
+ }
11902
+
11879
11903
  if (resizer) {
11880
11904
  let isResizing = false
11881
11905
  let pointerId = null
@@ -11952,11 +11976,225 @@ document.addEventListener("DOMContentLoaded", () => {
11952
11976
  openWithAgent,
11953
11977
  openPicker,
11954
11978
  openWithUrl,
11979
+ openDesktopEvent,
11955
11980
  close: () => setOpen(false),
11956
11981
  isOpen: () => open
11957
11982
  }
11958
11983
  })();
11959
11984
 
11985
+ (function() {
11986
+ const normalizeLaunchUrl = (rawUrl) => {
11987
+ if (typeof rawUrl !== "string" || !rawUrl.trim()) {
11988
+ return null
11989
+ }
11990
+ let parsed
11991
+ try {
11992
+ parsed = new URL(rawUrl, window.location.origin)
11993
+ } catch (_) {
11994
+ return null
11995
+ }
11996
+ if (parsed.origin !== window.location.origin) {
11997
+ return null
11998
+ }
11999
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`
12000
+ }
12001
+
12002
+ const isTrustedOrigin = (origin) => {
12003
+ if (!origin) {
12004
+ return true
12005
+ }
12006
+ let parsed
12007
+ try {
12008
+ parsed = new URL(origin)
12009
+ } catch (_) {
12010
+ return false
12011
+ }
12012
+ const originPort = parsed.port || "80"
12013
+ const currentPort = window.location.port || "80"
12014
+ if (String(originPort) === String(currentPort)) {
12015
+ return true
12016
+ }
12017
+ return /https:\/\/.*pinokio\..*(localhost|co)/.test(origin)
12018
+ }
12019
+
12020
+ const escapeText = (value) => String(value || "")
12021
+ .replace(/&/g, "&amp;")
12022
+ .replace(/</g, "&lt;")
12023
+ .replace(/>/g, "&gt;")
12024
+ .replace(/"/g, "&quot;")
12025
+ .replace(/'/g, "&#39;")
12026
+
12027
+ let overlayEl = null
12028
+ let overlayEscHandler = null
12029
+
12030
+ const closeOverlay = () => {
12031
+ if (overlayEscHandler) {
12032
+ document.removeEventListener("keydown", overlayEscHandler)
12033
+ overlayEscHandler = null
12034
+ }
12035
+ if (overlayEl && overlayEl.parentNode) {
12036
+ overlayEl.parentNode.removeChild(overlayEl)
12037
+ }
12038
+ overlayEl = null
12039
+ }
12040
+
12041
+ const openOverlay = (launch) => {
12042
+ if (!launch || typeof launch !== "object") {
12043
+ return false
12044
+ }
12045
+ const normalizedUrl = normalizeLaunchUrl(launch.url)
12046
+ if (!normalizedUrl) {
12047
+ return false
12048
+ }
12049
+ closeOverlay()
12050
+ const ui = launch.ui && typeof launch.ui === "object" ? launch.ui : {}
12051
+ const title = typeof ui.title === "string" && ui.title.trim()
12052
+ ? ui.title.trim()
12053
+ : "Task"
12054
+ const overlay = document.createElement("div")
12055
+ overlay.className = "pinokio-custom-terminal-overlay pinokio-desktop-event-overlay"
12056
+ overlay.innerHTML = `
12057
+ <div class="pinokio-custom-terminal-modal">
12058
+ <div class="pinokio-custom-terminal-header">
12059
+ <h3>${escapeText(title)}</h3>
12060
+ <button class="pinokio-custom-terminal-close" aria-label="Close">×</button>
12061
+ </div>
12062
+ <div class="pinokio-custom-terminal-body">
12063
+ <iframe src="${escapeText(normalizedUrl)}" frameborder="0" allow="fullscreen *;" allowfullscreen></iframe>
12064
+ </div>
12065
+ </div>
12066
+ `
12067
+ const closeBtn = overlay.querySelector(".pinokio-custom-terminal-close")
12068
+ if (closeBtn) {
12069
+ closeBtn.addEventListener("click", closeOverlay)
12070
+ }
12071
+ overlay.addEventListener("click", (event) => {
12072
+ if (event.target === overlay) {
12073
+ closeOverlay()
12074
+ }
12075
+ })
12076
+ overlayEscHandler = (event) => {
12077
+ if (event.key === "Escape") {
12078
+ closeOverlay()
12079
+ }
12080
+ }
12081
+ document.addEventListener("keydown", overlayEscHandler)
12082
+ document.body.appendChild(overlay)
12083
+ overlayEl = overlay
12084
+ return true
12085
+ }
12086
+
12087
+ const openSidebar = (launch) => {
12088
+ const drawer = window.PinokioAskAiDrawer
12089
+ if (!drawer || typeof drawer.openDesktopEvent !== "function") {
12090
+ return false
12091
+ }
12092
+ closeOverlay()
12093
+ return drawer.openDesktopEvent(launch)
12094
+ }
12095
+
12096
+ const handleDesktopLaunch = (envelope) => {
12097
+ const launch = envelope && typeof envelope === "object" && envelope.launch && typeof envelope.launch === "object"
12098
+ ? envelope.launch
12099
+ : envelope
12100
+ if (!launch || typeof launch !== "object") {
12101
+ return false
12102
+ }
12103
+ const ui = launch.ui && typeof launch.ui === "object" ? launch.ui : {}
12104
+ const mode = typeof ui.mode === "string" ? ui.mode.trim().toLowerCase() : "sidebar"
12105
+ if (mode === "overlay") {
12106
+ return openOverlay(launch)
12107
+ }
12108
+ if (mode === "sidebar") {
12109
+ const opened = openSidebar(launch)
12110
+ if (opened) {
12111
+ return true
12112
+ }
12113
+ return openOverlay(launch)
12114
+ }
12115
+ return false
12116
+ }
12117
+
12118
+ const dispatchDesktopEvent = async (envelope) => {
12119
+ if (!envelope || typeof envelope !== "object") {
12120
+ return null
12121
+ }
12122
+ const eventName = typeof envelope.event === "string" ? envelope.event.trim() : ""
12123
+ if (!eventName) {
12124
+ return null
12125
+ }
12126
+ const payload = envelope.payload && typeof envelope.payload === "object"
12127
+ ? envelope.payload
12128
+ : {}
12129
+ const context = envelope.context && typeof envelope.context === "object"
12130
+ ? { ...envelope.context }
12131
+ : {}
12132
+ if (!context.pageUrl) {
12133
+ context.pageUrl = window.location.href
12134
+ }
12135
+ if (!context.currentUrl) {
12136
+ context.currentUrl = window.location.href
12137
+ }
12138
+ if (!context.referrerUrl && document.referrer) {
12139
+ context.referrerUrl = document.referrer
12140
+ }
12141
+ const response = await fetch("/pinokio/desktop/event", {
12142
+ method: "POST",
12143
+ headers: {
12144
+ "Content-Type": "application/json"
12145
+ },
12146
+ body: JSON.stringify({ event: eventName, payload, context })
12147
+ })
12148
+ if (!response.ok) {
12149
+ return null
12150
+ }
12151
+ return response.json().catch(() => null)
12152
+ }
12153
+
12154
+ window.addEventListener("message", (event) => {
12155
+ if (!event || !event.data || typeof event.data !== "object") {
12156
+ return
12157
+ }
12158
+ if (event.data.e !== "pinokio:desktop-event") {
12159
+ return
12160
+ }
12161
+ dispatchDesktopEvent(event.data).then((result) => {
12162
+ if (!result || !result.ok || !result.matched || !result.launch || !result.launch.url) {
12163
+ return
12164
+ }
12165
+ window.postMessage({ e: "pinokio:desktop-event-launch", launch: result.launch }, "*")
12166
+ }).catch(() => {})
12167
+ })
12168
+
12169
+ window.addEventListener("pinokio:desktop-event-launch", (event) => {
12170
+ handleDesktopLaunch(event && event.detail ? event.detail : null)
12171
+ })
12172
+
12173
+ window.addEventListener("message", (event) => {
12174
+ if (!event || !event.data || typeof event.data !== "object") {
12175
+ return
12176
+ }
12177
+ if (event.data.e !== "pinokio:desktop-event-launch") {
12178
+ return
12179
+ }
12180
+ if (!isTrustedOrigin(event.origin)) {
12181
+ return
12182
+ }
12183
+ handleDesktopLaunch(event.data)
12184
+ })
12185
+
12186
+ window.PinokioDesktopEventUi = {
12187
+ open: (launch) => handleDesktopLaunch(launch),
12188
+ closeOverlay,
12189
+ closeSidebar: () => {
12190
+ const drawer = window.PinokioAskAiDrawer
12191
+ if (drawer && typeof drawer.close === "function") {
12192
+ drawer.close()
12193
+ }
12194
+ }
12195
+ }
12196
+ })();
12197
+
11960
12198
  (function() {
11961
12199
  const drawer = document.getElementById("community-drawer")
11962
12200
  if (!drawer) {