pinokiod 8.2.18 → 8.2.19

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": "8.2.18",
3
+ "version": "8.2.19",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -1037,7 +1037,7 @@ class Server {
1037
1037
  const entries = await this.kernel.api.listApps()
1038
1038
  return { apps: entries.map((entry) => ({ id: entry.id, name: entry.title || entry.name || entry.id, icon: entry.icon })) }
1039
1039
  }
1040
- // launch is a side effect: only pinokio.co and this computer may call it
1040
+ // launch and stop are side effects: only pinokio.co and this computer may call them
1041
1041
  anywhereOriginAllowed(origin) {
1042
1042
  if (!origin) return true
1043
1043
  let parsed
@@ -1067,10 +1067,15 @@ class Server {
1067
1067
  }
1068
1068
  return items
1069
1069
  }
1070
+ // the folder of an installed app, or "" for anything that is not exactly one folder name under api/
1071
+ async anywhereAppDir(id) {
1072
+ const dir = typeof id === "string" ? this.kernel.path("api", id) : ""
1073
+ if (!dir || path.dirname(dir) !== this.kernel.path("api") || path.basename(dir) !== id) return ""
1074
+ return (await this.kernel.api.exists(dir)) ? dir : ""
1075
+ }
1070
1076
  async composeAnywhereLaunch({ id, href } = {}) {
1071
1077
  const api = this.kernel.api
1072
- const dir = typeof id === "string" ? this.kernel.path("api", id) : ""
1073
- if (!dir || path.dirname(dir) !== this.kernel.path("api") || path.basename(dir) !== id || !(await api.exists(dir))) {
1078
+ if (!(await this.anywhereAppDir(id))) {
1074
1079
  return { status: 400, body: { error: `Unknown app: ${id}` } }
1075
1080
  }
1076
1081
  const menu = await this.anywhereMenu(api, await api.launcher_path(id))
@@ -1104,6 +1109,21 @@ class Server {
1104
1109
  }
1105
1110
  return { status: 200, body: { action: "started" } }
1106
1111
  }
1112
+ // stops every run under the app's folder the way the desktop stops one (kernel.api.stop by its running key)
1113
+ async composeAnywhereStop({ id } = {}) {
1114
+ const api = this.kernel.api
1115
+ const dir = await this.anywhereAppDir(id)
1116
+ if (!dir) {
1117
+ return { status: 400, body: { error: `Unknown app: ${id}` } }
1118
+ }
1119
+ for (const [key, value] of Object.entries(api.running || {})) {
1120
+ const rel = path.relative(dir, key.split("?")[0])
1121
+ if (value && rel && !rel.startsWith("..") && !path.isAbsolute(rel)) {
1122
+ await api.stop({ params: { id: key } })
1123
+ }
1124
+ }
1125
+ return { status: 200, body: { action: "stopped" } }
1126
+ }
1107
1127
  async composeAnywhereStatus({ light } = {}) {
1108
1128
  const anywhere = this.kernel ? this.kernel.anywhere : null
1109
1129
  if (!anywhere) {
@@ -14349,19 +14369,22 @@ class Server {
14349
14369
  res.set("Cache-Control", "no-store")
14350
14370
  res.json(await this.composeAnywhereApps())
14351
14371
  }))
14352
- this.app.post("/anywhere/launch", ex(async (req, res) => {
14372
+ // app actions change what runs on this computer: pinokio.co and this computer only
14373
+ const anywhereAppAction = (fn) => ex(async (req, res) => {
14353
14374
  res.set("Cache-Control", "no-store")
14354
14375
  if (!this.anywhereOriginAllowed(req.get("Origin"))) {
14355
- res.status(403).json({ error: "Launching is allowed only from pinokio.co or this computer" })
14376
+ res.status(403).json({ error: "Allowed only from pinokio.co or this computer" })
14356
14377
  return
14357
14378
  }
14358
14379
  try {
14359
- const { status, body } = await this.composeAnywhereLaunch(req.body || {})
14380
+ const { status, body } = await fn(req.body || {})
14360
14381
  res.status(status).json(body)
14361
14382
  } catch (error) {
14362
14383
  res.status(500).json({ error: error && error.message ? error.message : String(error) })
14363
14384
  }
14364
- }))
14385
+ })
14386
+ this.app.post("/anywhere/apps/launch", anywhereAppAction((body) => this.composeAnywhereLaunch(body)))
14387
+ this.app.post("/anywhere/apps/stop", anywhereAppAction((body) => this.composeAnywhereStop(body)))
14365
14388
  const anywhereAction = (label, fn) => ex(async (req, res) => {
14366
14389
  const anywhere = this.kernel.anywhere
14367
14390
  if (!anywhere) {
@@ -110,7 +110,7 @@ test('the catalog maps installed apps to id, name and icon', async () => {
110
110
  // a launch harness: one app "comfy" at /managed/api/comfy whose menu is set per case;
111
111
  // kernel.status mirrors the kernel's session-aware running check
112
112
  const launchHarness = ({ menu = [], running = {}, anywhere = readyAnywhere() } = {}) => {
113
- const calls = { process: [], cleared: [], reserved: [], counted: [] }
113
+ const calls = { process: [], cleared: [], reserved: [], counted: [], stopped: [] }
114
114
  const api = {
115
115
  exists: async (p) => p === '/managed/api/comfy',
116
116
  launcher_path: async (name) => `/managed/api/${name}`,
@@ -118,6 +118,7 @@ const launchHarness = ({ menu = [], running = {}, anywhere = readyAnywhere() } =
118
118
  running,
119
119
  logSessions: { reserveLaunch: async (uri) => { calls.reserved.push(uri) } },
120
120
  process: (req) => { calls.process.push(req); return Promise.resolve() },
121
+ stop: async (req) => { calls.stopped.push(req.params.id) },
121
122
  }
122
123
  const server = Object.assign(Object.create(Server.prototype), {
123
124
  kernel: {
@@ -128,7 +129,7 @@ const launchHarness = ({ menu = [], running = {}, anywhere = readyAnywhere() } =
128
129
  },
129
130
  appPreferences: { recordLaunchByPath: async (p, options) => { calls.counted.push([p, options.source]) } },
130
131
  })
131
- return { server, calls, launch: (body) => server.composeAnywhereLaunch(body) }
132
+ return { server, calls, launch: (body) => server.composeAnywhereLaunch(body), stop: (body) => server.composeAnywhereStop(body) }
132
133
  }
133
134
 
134
135
  test('launch opens a loopback URL default at its Anywhere address, null until Serve carries it, other URLs as they are', async () => {
@@ -207,7 +208,32 @@ test('a drive-relative app id is refused on Windows', { skip: process.platform !
207
208
  }
208
209
  })
209
210
 
210
- test('launch is allowed from pinokio.co and this computer only', () => {
211
+ test('stop ends every run under the app folder by its running key, and nothing else', async () => {
212
+ const running = {
213
+ '/managed/api/comfy/start.js': true,
214
+ '/managed/api/comfy/start.js&session=abc': true,
215
+ '/managed/api/comfy/sub/worker.js?cwd=/managed/api/comfy': true,
216
+ '/managed/api/comfy/old.js': false,
217
+ '/managed/api/comfyui/start.js': true,
218
+ '/managed/api/other/start.js': true,
219
+ }
220
+ const h = launchHarness({ running })
221
+ assert.deepEqual(await h.stop({ id: 'comfy' }), { status: 200, body: { action: 'stopped' } })
222
+ assert.deepEqual(h.calls.stopped, [
223
+ '/managed/api/comfy/start.js',
224
+ '/managed/api/comfy/start.js&session=abc',
225
+ '/managed/api/comfy/sub/worker.js?cwd=/managed/api/comfy',
226
+ ])
227
+ const idle = launchHarness()
228
+ assert.deepEqual(await idle.stop({ id: 'comfy' }), { status: 200, body: { action: 'stopped' } })
229
+ assert.deepEqual(idle.calls.stopped, [])
230
+ for (const id of ['nope', '../comfy', 'comfy/start.js', '', undefined]) {
231
+ assert.equal((await h.stop({ id })).status, 400, String(id))
232
+ }
233
+ assert.equal(h.calls.stopped.length, 3)
234
+ })
235
+
236
+ test('launch and stop are allowed from pinokio.co and this computer only', () => {
211
237
  const server = Object.assign(Object.create(Server.prototype), { kernel: { anywhere: readyAnywhere() } })
212
238
  for (const origin of [undefined, 'https://pinokio.co', 'http://localhost:42000', 'http://127.0.0.1:42000', 'https://pinokio.tail1.ts.net']) {
213
239
  assert.equal(server.anywhereOriginAllowed(origin), true, String(origin))