pinokiod 3.270.0 → 3.272.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.
Files changed (56) hide show
  1. package/kernel/ansi_stream_tracker.js +115 -0
  2. package/kernel/api/app/index.js +422 -0
  3. package/kernel/api/htmlmodal/index.js +94 -0
  4. package/kernel/app_launcher/index.js +115 -0
  5. package/kernel/app_launcher/platform/base.js +276 -0
  6. package/kernel/app_launcher/platform/linux.js +229 -0
  7. package/kernel/app_launcher/platform/macos.js +163 -0
  8. package/kernel/app_launcher/platform/unsupported.js +34 -0
  9. package/kernel/app_launcher/platform/windows.js +247 -0
  10. package/kernel/bin/conda-meta.js +93 -0
  11. package/kernel/bin/conda.js +2 -4
  12. package/kernel/bin/index.js +2 -4
  13. package/kernel/index.js +9 -2
  14. package/kernel/peer.js +186 -19
  15. package/kernel/shell.js +212 -1
  16. package/package.json +1 -1
  17. package/server/index.js +491 -6
  18. package/server/public/common.js +224 -741
  19. package/server/public/create-launcher.js +754 -0
  20. package/server/public/htmlmodal.js +292 -0
  21. package/server/public/logs.js +715 -0
  22. package/server/public/resizeSync.js +117 -0
  23. package/server/public/style.css +651 -6
  24. package/server/public/tab-idle-notifier.js +34 -59
  25. package/server/public/tab-link-popover.js +7 -10
  26. package/server/public/terminal-settings.js +723 -9
  27. package/server/public/terminal_input_utils.js +72 -0
  28. package/server/public/terminal_key_caption.js +187 -0
  29. package/server/public/urldropdown.css +120 -3
  30. package/server/public/xterm-inline-bridge.js +116 -0
  31. package/server/socket.js +29 -0
  32. package/server/views/agents.ejs +1 -2
  33. package/server/views/app.ejs +55 -28
  34. package/server/views/bookmarklet.ejs +1 -1
  35. package/server/views/bootstrap.ejs +1 -0
  36. package/server/views/connect.ejs +1 -2
  37. package/server/views/create.ejs +63 -0
  38. package/server/views/editor.ejs +36 -4
  39. package/server/views/index.ejs +1 -2
  40. package/server/views/index2.ejs +1 -2
  41. package/server/views/init/index.ejs +36 -28
  42. package/server/views/install.ejs +20 -22
  43. package/server/views/layout.ejs +2 -8
  44. package/server/views/logs.ejs +155 -0
  45. package/server/views/mini.ejs +0 -18
  46. package/server/views/net.ejs +2 -2
  47. package/server/views/network.ejs +1 -2
  48. package/server/views/network2.ejs +1 -2
  49. package/server/views/old_network.ejs +1 -2
  50. package/server/views/pro.ejs +26 -23
  51. package/server/views/prototype/index.ejs +30 -34
  52. package/server/views/screenshots.ejs +1 -2
  53. package/server/views/settings.ejs +1 -20
  54. package/server/views/shell.ejs +59 -66
  55. package/server/views/terminal.ejs +118 -73
  56. package/server/views/tools.ejs +1 -2
@@ -0,0 +1,115 @@
1
+ const VOLATILE_CSI = new Set(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'P', 'S', 'T', 'X', '@', '`', 'd', 'e', 'f']);
2
+
3
+ class AnsiStreamTracker {
4
+ constructor() {
5
+ this.state = 'text';
6
+ this.params = '';
7
+ this.awaitingPrintable = false;
8
+ this.stringAllowsBell = false;
9
+ }
10
+
11
+ markVolatile() {
12
+ this.awaitingPrintable = true;
13
+ }
14
+
15
+ push(chunk = '') {
16
+ if (!chunk || typeof chunk !== 'string') {
17
+ return null;
18
+ }
19
+ let triggered = false;
20
+ let reason;
21
+ const setTriggered = (why) => {
22
+ if (!reason) {
23
+ reason = why;
24
+ }
25
+ triggered = true;
26
+ };
27
+
28
+ for (let i = 0; i < chunk.length; i++) {
29
+ const ch = chunk[i];
30
+ if (this.state === 'text') {
31
+ if (ch === '\u001b') {
32
+ this.state = 'esc';
33
+ } else if (ch === '\r' || ch === '\b') {
34
+ this.markVolatile();
35
+ } else if (ch === '\n') {
36
+ this.awaitingPrintable = false;
37
+ } else if (ch > ' ' && ch !== '\u007f') {
38
+ if (this.awaitingPrintable) {
39
+ this.awaitingPrintable = false;
40
+ setTriggered('line-rewrite');
41
+ }
42
+ }
43
+ continue;
44
+ }
45
+
46
+ if (this.state === 'esc') {
47
+ if (ch === '[') {
48
+ this.state = 'csi';
49
+ this.params = '';
50
+ } else if (ch === ']') {
51
+ this.state = 'osc';
52
+ this.stringAllowsBell = true;
53
+ } else if ('PX^_'.includes(ch)) {
54
+ this.state = 'osc';
55
+ this.stringAllowsBell = false;
56
+ } else {
57
+ if ('78MDE'.includes(ch)) {
58
+ this.markVolatile();
59
+ }
60
+ this.state = 'text';
61
+ }
62
+ continue;
63
+ }
64
+
65
+ if (this.state === 'osc') {
66
+ if (this.stringAllowsBell && ch === '\u0007') {
67
+ this.state = 'text';
68
+ this.stringAllowsBell = false;
69
+ } else if (ch === '\u001b') {
70
+ this.state = 'osc_esc';
71
+ }
72
+ continue;
73
+ }
74
+
75
+ if (this.state === 'osc_esc') {
76
+ if (ch === '\\') {
77
+ this.state = 'text';
78
+ this.stringAllowsBell = false;
79
+ } else {
80
+ this.state = 'osc';
81
+ }
82
+ continue;
83
+ }
84
+
85
+ if (this.state === 'csi') {
86
+ if (ch === '\u001b') {
87
+ this.state = 'esc';
88
+ continue;
89
+ }
90
+ if (ch >= '@' && ch <= '~') {
91
+ const finalChar = ch;
92
+ const params = this.params;
93
+ if ((finalChar === 'h' || finalChar === 'l') && params.includes('2026')) {
94
+ setTriggered('decsync');
95
+ this.awaitingPrintable = false;
96
+ } else if (VOLATILE_CSI.has(finalChar)) {
97
+ this.markVolatile();
98
+ }
99
+ this.state = 'text';
100
+ this.params = '';
101
+ } else {
102
+ this.params += ch;
103
+ }
104
+ continue;
105
+ }
106
+ }
107
+
108
+ if (triggered) {
109
+ return { reason: reason || 'line-rewrite' };
110
+ }
111
+ return null;
112
+ }
113
+ }
114
+
115
+ module.exports = AnsiStreamTracker;
@@ -0,0 +1,422 @@
1
+ const fs = require('fs')
2
+ const { promisify } = require('util')
3
+ const { execFile } = require('child_process')
4
+ const HtmlModal = require('../htmlmodal')
5
+ const Util = require('../../util')
6
+
7
+ const DEFAULT_INSTALL_TIMEOUT = 5 * 60 * 1000
8
+ const DEFAULT_INSTALL_INTERVAL = 5000
9
+ const execFileAsync = promisify(execFile)
10
+
11
+ const escapeHtml = (value = '') => {
12
+ return String(value).replace(/[&<>"']/g, (char) => {
13
+ switch (char) {
14
+ case '&':
15
+ return '&amp;'
16
+ case '<':
17
+ return '&lt;'
18
+ case '>':
19
+ return '&gt;'
20
+ case '"':
21
+ return '&quot;'
22
+ case '\'':
23
+ return '&#39;'
24
+ default:
25
+ return char
26
+ }
27
+ })
28
+ }
29
+
30
+ class AppAPI {
31
+ constructor() {
32
+ this.htmlModal = new HtmlModal()
33
+ }
34
+
35
+ ensureService(kernel) {
36
+ if (!kernel.appLauncher) {
37
+ throw new Error('App launcher service is unavailable')
38
+ }
39
+ return kernel.appLauncher
40
+ }
41
+
42
+ async launch(req, ondata, kernel) {
43
+ /*
44
+ {
45
+ "method": "app.launch",
46
+ "params": {
47
+ "id": <optional app id>,
48
+ "app": <name when id missing>,
49
+ "args": [<arg>, ...],
50
+ "refresh": <force reindex boolean>,
51
+ "install": <install url>,
52
+ "installTimeout": <ms to wait>,
53
+ "installPollInterval": <poll frequency ms>
54
+ }
55
+ }
56
+ */
57
+ const params = req.params || {}
58
+ const launcher = this.ensureService(kernel)
59
+ try {
60
+ const result = await launcher.launch({
61
+ id: params.id,
62
+ app: params.app || params.name,
63
+ args: params.args,
64
+ refresh: params.refresh,
65
+ install: params.install
66
+ })
67
+ return result
68
+ } catch (error) {
69
+ if (params.install && error && error.code === 'APP_NOT_FOUND') {
70
+ return await this.handleInstallFlow({ req, ondata, kernel, launcher, params })
71
+ }
72
+ throw error
73
+ }
74
+ }
75
+
76
+ async search(req, ondata, kernel) {
77
+ /*
78
+ {
79
+ "method": "app.search",
80
+ "params": {
81
+ "query": <text>,
82
+ "limit": <max results>,
83
+ "refresh": <force reindex boolean>
84
+ }
85
+ }
86
+ */
87
+ const params = req.params || {}
88
+ const launcher = this.ensureService(kernel)
89
+ return launcher.search({
90
+ query: params.query || params.app || params.name || '',
91
+ limit: params.limit || 25,
92
+ refresh: params.refresh
93
+ })
94
+ }
95
+
96
+ async info(req, ondata, kernel) {
97
+ /*
98
+ {
99
+ "method": "app.info",
100
+ "params": {
101
+ "id": <required app id>,
102
+ "refresh": <force reindex boolean>
103
+ }
104
+ }
105
+ */
106
+ const params = req.params || {}
107
+ if (!params.id) {
108
+ throw new Error('app.info requires params.id')
109
+ }
110
+ const launcher = this.ensureService(kernel)
111
+ return launcher.info({ id: params.id, refresh: params.refresh })
112
+ }
113
+
114
+ async refresh(req, ondata, kernel) {
115
+ /*
116
+ {
117
+ "method": "app.refresh",
118
+ "params": {
119
+ "force": <optional flag propagated to adapter>
120
+ }
121
+ }
122
+ */
123
+ const launcher = this.ensureService(kernel)
124
+ return launcher.refresh(req && req.params ? req.params : {})
125
+ }
126
+ }
127
+
128
+ AppAPI.prototype.sleep = function sleep(ms) {
129
+ return new Promise((resolve) => setTimeout(resolve, ms))
130
+ }
131
+
132
+ AppAPI.prototype.modalRequest = function modalRequest(req, modalId, params = {}) {
133
+ return {
134
+ params: Object.assign({ id: modalId }, params),
135
+ parent: req.parent,
136
+ cwd: req.cwd
137
+ }
138
+ }
139
+
140
+ AppAPI.prototype.buildInstallActions = function buildInstallActions(appName, installUrl, extraActions = []) {
141
+ const actions = []
142
+ if (installUrl) {
143
+ actions.push({
144
+ id: 'install-link',
145
+ label: `Install ${appName}`,
146
+ type: 'link',
147
+ href: installUrl,
148
+ variant: 'link',
149
+ icon: 'fa-solid fa-arrow-up-right-from-square'
150
+ })
151
+ }
152
+ return actions.concat(extraActions || [])
153
+ }
154
+
155
+ AppAPI.prototype.buildReadyActions = function buildReadyActions({ appName, installUrl, entry, needsManualLaunch }) {
156
+ const actions = this.buildInstallActions(appName, installUrl)
157
+ if (needsManualLaunch) {
158
+ actions.push({
159
+ id: 'reveal',
160
+ label: 'Open in Finder',
161
+ type: 'submit',
162
+ variant: 'secondary',
163
+ close: false
164
+ })
165
+ actions.push({
166
+ id: 'confirm-launch',
167
+ label: `I've opened ${entry.name || appName}`,
168
+ type: 'submit',
169
+ primary: true,
170
+ close: false
171
+ })
172
+ actions.push({
173
+ id: 'cancel',
174
+ label: 'Cancel',
175
+ type: 'submit',
176
+ variant: 'secondary'
177
+ })
178
+ } else {
179
+ actions.push({
180
+ id: 'launch',
181
+ label: `Open ${entry.name || appName}`,
182
+ type: 'submit',
183
+ primary: true
184
+ })
185
+ actions.push({
186
+ id: 'cancel',
187
+ label: 'Cancel',
188
+ type: 'submit',
189
+ variant: 'secondary'
190
+ })
191
+ }
192
+ return actions
193
+ }
194
+
195
+ AppAPI.prototype.installIntroHtml = function installIntroHtml(appName, installUrl) {
196
+ const safeName = escapeHtml(appName)
197
+ const safeUrl = escapeHtml(installUrl)
198
+ return `
199
+ <p>Pinokio could not find <strong>${safeName}</strong> on this system.</p>
200
+ <p>Click <em>Install ${safeName}</em> to open the official download page (${safeUrl}). Keep this window open; Pinokio will monitor for the installation automatically.</p>
201
+ `
202
+ }
203
+
204
+ AppAPI.prototype.installReadyHtml = function installReadyHtml(appName) {
205
+ const safeName = escapeHtml(appName)
206
+ return `
207
+ <p><strong>${safeName}</strong> was detected successfully.</p>
208
+ <p>Click <em>Open ${safeName}</em> to launch the application now.</p>
209
+ `
210
+ }
211
+
212
+ AppAPI.prototype.manualLaunchHtml = function manualLaunchHtml(appName) {
213
+ const safeName = escapeHtml(appName)
214
+ return `
215
+ <p>macOS needs you to open <strong>${safeName}</strong> manually the first time.</p>
216
+ <ol>
217
+ <li>Click <em>Open in Finder</em> to reveal the app.</li>
218
+ <li>Double-click ${safeName} in Finder and choose <em>Open</em> when macOS asks for confirmation.</li>
219
+ <li>Return here and click <em>I've opened ${safeName}</em>.</li>
220
+ </ol>
221
+ `
222
+ }
223
+
224
+ AppAPI.prototype.handleInstallFlow = async function handleInstallFlow({ req, ondata, kernel, launcher, params }) {
225
+ const installUrl = params.install
226
+ if (!installUrl) {
227
+ throw new Error('Install URL is required when install flow is requested')
228
+ }
229
+ const appName = params.app || params.name || params.id || 'the application'
230
+ const modalId = `app-install:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`
231
+
232
+ const pollInterval = Number(params.installPollInterval) > 0 ? Number(params.installPollInterval) : DEFAULT_INSTALL_INTERVAL
233
+ const timeout = Number(params.installTimeout) > 0 ? Number(params.installTimeout) : DEFAULT_INSTALL_TIMEOUT
234
+
235
+ const actions = this.buildInstallActions(appName, installUrl)
236
+
237
+ await this.htmlModal.open(
238
+ this.modalRequest(req, modalId, {
239
+ title: `Install ${appName}`,
240
+ html: this.installIntroHtml(appName, installUrl),
241
+ status: { text: `Waiting for ${escapeHtml(appName)} to be installed...`, waiting: true },
242
+ actions,
243
+ dismissible: true
244
+ }),
245
+ ondata,
246
+ kernel
247
+ )
248
+
249
+ const entry = await this.waitForInstall({
250
+ req,
251
+ ondata,
252
+ kernel,
253
+ launcher,
254
+ appName,
255
+ modalId,
256
+ pollInterval,
257
+ timeout,
258
+ actions
259
+ })
260
+
261
+ if (!entry) {
262
+ await this.htmlModal.update(
263
+ this.modalRequest(req, modalId, {
264
+ status: { text: `Still cannot find ${escapeHtml(appName)}. Please complete the installation and try again.`, variant: 'error' },
265
+ actions: this.buildInstallActions(appName, installUrl, [{
266
+ id: 'close',
267
+ label: 'Close',
268
+ type: 'submit',
269
+ variant: 'secondary',
270
+ close: true
271
+ }]),
272
+ await: true
273
+ }),
274
+ ondata,
275
+ kernel
276
+ )
277
+ await this.htmlModal.close(this.modalRequest(req, modalId, {}), ondata, kernel)
278
+ throw new Error(`Timed out waiting for ${appName} to be installed`)
279
+ }
280
+
281
+ const needsManualLaunch = await this.requiresManualLaunch(entry, kernel)
282
+ const readyPayload = {
283
+ title: entry.name || appName,
284
+ html: needsManualLaunch ? this.manualLaunchHtml(entry.name || appName) : this.installReadyHtml(entry.name || appName),
285
+ status: needsManualLaunch
286
+ ? { text: `Open ${escapeHtml(entry.name || appName)} once from Finder, then confirm below.` }
287
+ : { text: `${escapeHtml(entry.name || appName)} detected.`, variant: 'success' },
288
+ actions: this.buildReadyActions({ appName, installUrl, entry, needsManualLaunch }),
289
+ await: true,
290
+ dismissible: true
291
+ }
292
+
293
+ let readyResponse
294
+ while (true) {
295
+ readyResponse = await this.htmlModal.update(
296
+ this.modalRequest(req, modalId, readyPayload),
297
+ ondata,
298
+ kernel
299
+ )
300
+ if (!readyResponse) {
301
+ continue
302
+ }
303
+ if (readyResponse.action === 'reveal') {
304
+ await this.openInExplorer(entry.path, kernel)
305
+ readyPayload.status = {
306
+ text: `Opened Finder at ${escapeHtml(entry.name || appName)}. After approving it, click "I've opened it".`,
307
+ }
308
+ continue
309
+ }
310
+ if (['cancel', 'close', 'dismissed'].includes(readyResponse.action)) {
311
+ await this.htmlModal.close(this.modalRequest(req, modalId, {}), ondata, kernel)
312
+ throw new Error('Launch cancelled by user')
313
+ }
314
+ if (readyResponse.action === 'launch' || readyResponse.action === 'confirm-launch') {
315
+ break
316
+ }
317
+ }
318
+
319
+ try {
320
+ const launchResult = await launcher.launch({
321
+ id: entry.id,
322
+ app: entry.name,
323
+ args: params.args
324
+ })
325
+ await this.htmlModal.update(
326
+ this.modalRequest(req, modalId, {
327
+ status: { text: `Opening ${escapeHtml(entry.name || appName)}...`, variant: 'success' },
328
+ actions: [],
329
+ dismissible: true
330
+ }),
331
+ ondata,
332
+ kernel
333
+ )
334
+ await this.htmlModal.close(this.modalRequest(req, modalId, {}), ondata, kernel)
335
+ return launchResult
336
+ } catch (err) {
337
+ await this.htmlModal.update(
338
+ this.modalRequest(req, modalId, {
339
+ status: { text: `Failed to launch ${escapeHtml(entry.name || appName)}: ${escapeHtml(err.message || 'Unknown error')}`, variant: 'error' },
340
+ actions: this.buildInstallActions(appName, installUrl, [{
341
+ id: 'close',
342
+ label: 'Close',
343
+ type: 'submit',
344
+ variant: 'secondary',
345
+ close: true
346
+ }]),
347
+ await: true
348
+ }),
349
+ ondata,
350
+ kernel
351
+ )
352
+ await this.htmlModal.close(this.modalRequest(req, modalId, {}), ondata, kernel)
353
+ throw err
354
+ }
355
+ }
356
+
357
+ AppAPI.prototype.waitForInstall = async function waitForInstall({ req, ondata, kernel, launcher, appName, modalId, pollInterval, timeout, actions }) {
358
+ const start = Date.now()
359
+ let attempt = 0
360
+ while ((Date.now() - start) < timeout) {
361
+ await this.sleep(pollInterval)
362
+ attempt += 1
363
+ try {
364
+ await launcher.refresh({ force: true })
365
+ } catch (_) {
366
+ }
367
+ const match = await launcher.findMatch(appName, { force: false })
368
+ if (match && match.entry) {
369
+ return match.entry
370
+ }
371
+ await this.htmlModal.update(
372
+ this.modalRequest(req, modalId, {
373
+ status: { text: `Waiting for ${escapeHtml(appName)} (check #${attempt})...`, waiting: true },
374
+ actions
375
+ }),
376
+ ondata,
377
+ kernel
378
+ )
379
+ }
380
+ return null
381
+ }
382
+
383
+ AppAPI.prototype.requiresManualLaunch = async function requiresManualLaunch(entry, kernel) {
384
+ if (!entry || kernel.platform !== 'darwin') {
385
+ return false
386
+ }
387
+ if (!entry.path) {
388
+ return false
389
+ }
390
+ try {
391
+ await fs.promises.access(entry.path)
392
+ } catch (_) {
393
+ return false
394
+ }
395
+ try {
396
+ await execFileAsync('xattr', ['-p', 'com.apple.quarantine', entry.path])
397
+ return true
398
+ } catch (error) {
399
+ if (error && typeof error.code !== 'undefined') {
400
+ if (error.code === 1) {
401
+ return false
402
+ }
403
+ }
404
+ if (error && error.stderr && /No such xattr/i.test(error.stderr)) {
405
+ return false
406
+ }
407
+ }
408
+ return false
409
+ }
410
+
411
+ AppAPI.prototype.openInExplorer = async function openInExplorer(targetPath, kernel) {
412
+ if (!targetPath) {
413
+ return
414
+ }
415
+ try {
416
+ Util.openfs(targetPath, { action: 'view' }, kernel)
417
+ } catch (error) {
418
+ console.warn('[app.launch] Failed to open file explorer:', error && error.message ? error.message : error)
419
+ }
420
+ }
421
+
422
+ module.exports = AppAPI
@@ -0,0 +1,94 @@
1
+ class HtmlModalAPI {
2
+ constructor() {
3
+ this.defaultIdPrefix = 'htmlmodal'
4
+ }
5
+
6
+ resolveParentPath(req) {
7
+ if (req && req.parent && req.parent.path) {
8
+ return req.parent.path
9
+ }
10
+ if (req && req.cwd) {
11
+ return req.cwd
12
+ }
13
+ if (req && req.params && req.params.id) {
14
+ return req.params.id
15
+ }
16
+ return this.defaultIdPrefix
17
+ }
18
+
19
+ buildPacket(req, action) {
20
+ const params = Object.assign({}, req.params || {})
21
+ if (!params.id) {
22
+ params.id = `${this.defaultIdPrefix}:${this.resolveParentPath(req)}`
23
+ }
24
+ return Object.assign({ action }, params)
25
+ }
26
+
27
+ async dispatch(req, ondata, kernel, action, options = {}) {
28
+ if (!req || typeof req !== 'object') {
29
+ req = { params: {} }
30
+ }
31
+ if (!req.params) {
32
+ req.params = {}
33
+ }
34
+ const packet = this.buildPacket(req, action)
35
+ if (options.forceAwait === false) {
36
+ packet.await = false
37
+ }
38
+ ondata(packet, 'htmlmodal')
39
+ if (packet.await) {
40
+ const waitKey = this.resolveParentPath(req)
41
+ const response = await kernel.api.wait(waitKey)
42
+ return response
43
+ }
44
+ return packet
45
+ }
46
+
47
+ async open(req, ondata, kernel) {
48
+ /*
49
+ {
50
+ "method": "htmlmodal.open",
51
+ "params": {
52
+ "id": <optional modal id>,
53
+ "title": <string>,
54
+ "html": <html string>,
55
+ "statusText": <string>,
56
+ "actions": [ { ... } ],
57
+ "await": <wait for response>
58
+ }
59
+ }
60
+ */
61
+ return this.dispatch(req, ondata, kernel, 'open')
62
+ }
63
+
64
+ async update(req, ondata, kernel) {
65
+ /*
66
+ {
67
+ "method": "htmlmodal.update",
68
+ "params": {
69
+ "id": <modal id>,
70
+ "html": <html string>,
71
+ "statusText": <string>,
72
+ "waiting": <bool>,
73
+ "actions": [ { ... } ],
74
+ "await": <wait for response>
75
+ }
76
+ }
77
+ */
78
+ return this.dispatch(req, ondata, kernel, 'update')
79
+ }
80
+
81
+ async close(req, ondata, kernel) {
82
+ /*
83
+ {
84
+ "method": "htmlmodal.close",
85
+ "params": {
86
+ "id": <modal id>
87
+ }
88
+ }
89
+ */
90
+ return this.dispatch(req, ondata, kernel, 'close', { forceAwait: false })
91
+ }
92
+ }
93
+
94
+ module.exports = HtmlModalAPI