pinokiod 1.0.17 → 1.0.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.
@@ -4,6 +4,7 @@ const path = require('path')
4
4
  const _ = require('lodash')
5
5
  const git = require('isomorphic-git')
6
6
  const http = require('isomorphic-git/http/node')
7
+ const Lproxy = require('lproxy')
7
8
  //const { glob, globSync, globStream, globStreamSync, Glob, } = require('glob')
8
9
  const { glob, sync, hasMagic } = require('glob-gitignore')
9
10
  const fastq = require('fastq')
@@ -20,7 +21,54 @@ class Api {
20
21
  this.running = {}
21
22
  this.done = {}
22
23
  this.waiter = {}
23
- this.prosies = {}
24
+ this.proxies = {}
25
+ this.lproxy = new Lproxy()
26
+ }
27
+ async startProxy(scriptPath, uri, name) {
28
+ console.log("startProxy", { scriptPath, uri, proxies: this.proxies })
29
+ let proxy_uri = await this.lproxy.start(uri)
30
+ console.log({ proxy_uri, uri })
31
+ if (this.proxies[scriptPath]) {
32
+ this.proxies[scriptPath].push({ target: uri, proxy: proxy_uri, name})
33
+ } else {
34
+ this.proxies[scriptPath] = [{ target: uri, proxy: proxy_uri, name }]
35
+ }
36
+ return {
37
+ target: uri,
38
+ proxy: proxy_uri
39
+ }
40
+ console.log("started", { proxies: this.proxies })
41
+ }
42
+ stopProxy(config) {
43
+ console.log("stopProxy", { config, proxies: this.proxies })
44
+ if (config.script) {
45
+ // stop all proxies for the script
46
+ if (this.proxies[config.script]) {
47
+ for(let p of this.proxies[config.script]) {
48
+ this.stopProxy({ uri: p.target })
49
+ }
50
+ }
51
+ } else if (config.uri) {
52
+
53
+ // stop the proxy
54
+ this.lproxy.stop(config.uri)
55
+
56
+ // remove the uri from the proxies array
57
+ for(let key in this.proxies) {
58
+ let p = this.proxies[key]
59
+ if (p.includes(config.uri)) {
60
+ let index;
61
+ for(let i = 0; i<p.length; i++) {
62
+ if (p[i].target === config.uri) {
63
+ index = i
64
+ break;
65
+ }
66
+ }
67
+ this.proxies[key].splice(index, 1)
68
+ }
69
+ }
70
+ }
71
+ console.log("stopped", { proxies: this.proxies })
24
72
  }
25
73
  async init() {
26
74
  console.log("api.init", { homedir: this.kernel.homedir })
@@ -57,6 +105,9 @@ class Api {
57
105
  // if any process is in a "wait" state, resume it
58
106
  this.kernel.resumeprocess(requestPath)
59
107
 
108
+ // stop all proxies
109
+ this.stopProxy({ script: requestPath })
110
+
60
111
  // if there are any pending waiters, delete them
61
112
 
62
113
  if (this.waiter[requestPath]) {
@@ -1,20 +1,16 @@
1
- const Lproxy = require('lproxy')
2
1
  class P {
3
- constructor() {
4
- this.proxy = new Lproxy()
5
- }
6
2
  async start (req, ondata, kernel) {
7
3
  /*
8
4
  {
9
5
  "method": "proxy.start",
10
6
  "params": {
7
+ "name",
11
8
  "uri": "http://localhost:8192"
12
9
  }
13
10
  }
14
11
  */
15
- let { proxy, url } = await this.proxy.start(req.params.uri)
16
- this.kernel.api.proxies[req.parent.path] = proxy
17
- return { proxy, url }
12
+ let response = await kernel.api.startProxy(req.parent.path, req.params.uri, req.params.name)
13
+ return response
18
14
  }
19
15
  async stop (req, ondata, kernel) {
20
16
  /*
@@ -25,7 +21,7 @@ class P {
25
21
  }
26
22
  }
27
23
  */
28
- this.proxy.stop(req.params.uri)
24
+ kernel.api.stopProxy({ uri: req.params.uri })
29
25
  }
30
26
  }
31
27
  module.exports = P
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -741,7 +741,8 @@ class Server {
741
741
  //current: encodeURIComponent(req.originalUrl),
742
742
  current: req.originalUrl,
743
743
  editorUrl,
744
- execUrl: "~" + req.originalUrl.replace(/^\/_api/, "\/api")
744
+ execUrl: "~" + req.originalUrl.replace(/^\/_api/, "\/api"),
745
+ proxies: this.kernel.api.proxies[filepath],
745
746
  }
746
747
  console.log("RESULT", result)
747
748
 
@@ -0,0 +1,266 @@
1
+ // from https://github.com/sindresorhus/normalize-url
2
+ const normalizeURL = (() => {
3
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
4
+ const DATA_URL_DEFAULT_MIME_TYPE = 'text/plain';
5
+ const DATA_URL_DEFAULT_CHARSET = 'us-ascii';
6
+
7
+ const testParameter = (name, filters) => filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name);
8
+
9
+ const normalizeDataURL = (urlString, {stripHash}) => {
10
+ const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString);
11
+
12
+ if (!match) {
13
+ throw new Error(`Invalid URL: ${urlString}`);
14
+ }
15
+
16
+ let {type, data, hash} = match.groups;
17
+ const mediaType = type.split(';');
18
+ hash = stripHash ? '' : hash;
19
+
20
+ let isBase64 = false;
21
+ if (mediaType[mediaType.length - 1] === 'base64') {
22
+ mediaType.pop();
23
+ isBase64 = true;
24
+ }
25
+
26
+ // Lowercase MIME type
27
+ const mimeType = (mediaType.shift() || '').toLowerCase();
28
+ const attributes = mediaType
29
+ .map(attribute => {
30
+ let [key, value = ''] = attribute.split('=').map(string => string.trim());
31
+
32
+ // Lowercase `charset`
33
+ if (key === 'charset') {
34
+ value = value.toLowerCase();
35
+
36
+ if (value === DATA_URL_DEFAULT_CHARSET) {
37
+ return '';
38
+ }
39
+ }
40
+
41
+ return `${key}${value ? `=${value}` : ''}`;
42
+ })
43
+ .filter(Boolean);
44
+
45
+ const normalizedMediaType = [
46
+ ...attributes,
47
+ ];
48
+
49
+ if (isBase64) {
50
+ normalizedMediaType.push('base64');
51
+ }
52
+
53
+ if (normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)) {
54
+ normalizedMediaType.unshift(mimeType);
55
+ }
56
+
57
+ return `data:${normalizedMediaType.join(';')},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ''}`;
58
+ };
59
+
60
+ function normalizeUrl(urlString, options) {
61
+ options = {
62
+ defaultProtocol: 'http:',
63
+ normalizeProtocol: true,
64
+ forceHttp: false,
65
+ forceHttps: false,
66
+ stripAuthentication: true,
67
+ stripHash: false,
68
+ stripTextFragment: true,
69
+ stripWWW: true,
70
+ removeQueryParameters: [/^utm_\w+/i],
71
+ removeTrailingSlash: true,
72
+ removeSingleSlash: true,
73
+ removeDirectoryIndex: false,
74
+ removeExplicitPort: false,
75
+ sortQueryParameters: true,
76
+ ...options,
77
+ };
78
+
79
+ urlString = urlString.trim();
80
+
81
+ // Data URL
82
+ if (/^data:/i.test(urlString)) {
83
+ return normalizeDataURL(urlString, options);
84
+ }
85
+
86
+ if (/^view-source:/i.test(urlString)) {
87
+ throw new Error('`view-source:` is not supported as it is a non-standard protocol');
88
+ }
89
+
90
+ const hasRelativeProtocol = urlString.startsWith('//');
91
+ const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString);
92
+
93
+ // Prepend protocol
94
+ if (!isRelativeUrl) {
95
+ urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol);
96
+ }
97
+
98
+ const urlObject = new URL(urlString);
99
+
100
+ if (options.forceHttp && options.forceHttps) {
101
+ throw new Error('The `forceHttp` and `forceHttps` options cannot be used together');
102
+ }
103
+
104
+ if (options.forceHttp && urlObject.protocol === 'https:') {
105
+ urlObject.protocol = 'http:';
106
+ }
107
+
108
+ if (options.forceHttps && urlObject.protocol === 'http:') {
109
+ urlObject.protocol = 'https:';
110
+ }
111
+
112
+ // Remove auth
113
+ if (options.stripAuthentication) {
114
+ urlObject.username = '';
115
+ urlObject.password = '';
116
+ }
117
+
118
+ // Remove hash
119
+ if (options.stripHash) {
120
+ urlObject.hash = '';
121
+ } else if (options.stripTextFragment) {
122
+ urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, '');
123
+ }
124
+
125
+ // Remove duplicate slashes if not preceded by a protocol
126
+ // NOTE: This could be implemented using a single negative lookbehind
127
+ // regex, but we avoid that to maintain compatibility with older js engines
128
+ // which do not have support for that feature.
129
+ if (urlObject.pathname) {
130
+ // TODO: Replace everything below with `urlObject.pathname = urlObject.pathname.replace(/(?<!\b[a-z][a-z\d+\-.]{1,50}:)\/{2,}/g, '/');` when Safari supports negative lookbehind.
131
+
132
+ // Split the string by occurrences of this protocol regex, and perform
133
+ // duplicate-slash replacement on the strings between those occurrences
134
+ // (if any).
135
+ const protocolRegex = /\b[a-z][a-z\d+\-.]{1,50}:\/\//g;
136
+
137
+ let lastIndex = 0;
138
+ let result = '';
139
+ for (;;) {
140
+ const match = protocolRegex.exec(urlObject.pathname);
141
+ if (!match) {
142
+ break;
143
+ }
144
+
145
+ const protocol = match[0];
146
+ const protocolAtIndex = match.index;
147
+ const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex);
148
+
149
+ result += intermediate.replace(/\/{2,}/g, '/');
150
+ result += protocol;
151
+ lastIndex = protocolAtIndex + protocol.length;
152
+ }
153
+
154
+ const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length);
155
+ result += remnant.replace(/\/{2,}/g, '/');
156
+
157
+ urlObject.pathname = result;
158
+ }
159
+
160
+ // Decode URI octets
161
+ if (urlObject.pathname) {
162
+ try {
163
+ urlObject.pathname = decodeURI(urlObject.pathname);
164
+ } catch {}
165
+ }
166
+
167
+ // Remove directory index
168
+ if (options.removeDirectoryIndex === true) {
169
+ options.removeDirectoryIndex = [/^index\.[a-z]+$/];
170
+ }
171
+
172
+ if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {
173
+ let pathComponents = urlObject.pathname.split('/');
174
+ const lastComponent = pathComponents[pathComponents.length - 1];
175
+
176
+ if (testParameter(lastComponent, options.removeDirectoryIndex)) {
177
+ pathComponents = pathComponents.slice(0, -1);
178
+ urlObject.pathname = pathComponents.slice(1).join('/') + '/';
179
+ }
180
+ }
181
+
182
+ if (urlObject.hostname) {
183
+ // Remove trailing dot
184
+ urlObject.hostname = urlObject.hostname.replace(/\.$/, '');
185
+
186
+ // Remove `www.`
187
+ if (options.stripWWW && /^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)) {
188
+ // Each label should be max 63 at length (min: 1).
189
+ // Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
190
+ // Each TLD should be up to 63 characters long (min: 2).
191
+ // It is technically possible to have a single character TLD, but none currently exist.
192
+ urlObject.hostname = urlObject.hostname.replace(/^www\./, '');
193
+ }
194
+ }
195
+
196
+ // Remove query unwanted parameters
197
+ if (Array.isArray(options.removeQueryParameters)) {
198
+ // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy.
199
+ for (const key of [...urlObject.searchParams.keys()]) {
200
+ if (testParameter(key, options.removeQueryParameters)) {
201
+ urlObject.searchParams.delete(key);
202
+ }
203
+ }
204
+ }
205
+
206
+ if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) {
207
+ urlObject.search = '';
208
+ }
209
+
210
+ // Keep wanted query parameters
211
+ if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) {
212
+ // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy.
213
+ for (const key of [...urlObject.searchParams.keys()]) {
214
+ if (!testParameter(key, options.keepQueryParameters)) {
215
+ urlObject.searchParams.delete(key);
216
+ }
217
+ }
218
+ }
219
+
220
+ // Sort query parameters
221
+ if (options.sortQueryParameters) {
222
+ urlObject.searchParams.sort();
223
+
224
+ // Calling `.sort()` encodes the search parameters, so we need to decode them again.
225
+ try {
226
+ urlObject.search = decodeURIComponent(urlObject.search);
227
+ } catch {}
228
+ }
229
+
230
+ if (options.removeTrailingSlash) {
231
+ urlObject.pathname = urlObject.pathname.replace(/\/$/, '');
232
+ }
233
+
234
+ // Remove an explicit port number, excluding a default port number, if applicable
235
+ if (options.removeExplicitPort && urlObject.port) {
236
+ urlObject.port = '';
237
+ }
238
+
239
+ const oldUrlString = urlString;
240
+
241
+ // Take advantage of many of the Node `url` normalizations
242
+ urlString = urlObject.toString();
243
+
244
+ if (!options.removeSingleSlash && urlObject.pathname === '/' && !oldUrlString.endsWith('/') && urlObject.hash === '') {
245
+ urlString = urlString.replace(/\/$/, '');
246
+ }
247
+
248
+ // Remove ending `/` unless removeSingleSlash is false
249
+ if ((options.removeTrailingSlash || urlObject.pathname === '/') && urlObject.hash === '' && options.removeSingleSlash) {
250
+ urlString = urlString.replace(/\/$/, '');
251
+ }
252
+
253
+ // Restore relative protocol, if applicable
254
+ if (hasRelativeProtocol && !options.normalizeProtocol) {
255
+ urlString = urlString.replace(/^http:\/\//, '//');
256
+ }
257
+
258
+ // Remove http/https
259
+ if (options.stripProtocol) {
260
+ urlString = urlString.replace(/^(?:https?:)?\/\//, '');
261
+ }
262
+
263
+ return urlString;
264
+ }
265
+ return normalizeUrl
266
+ })();
@@ -14,6 +14,7 @@
14
14
  <script src="/nav.js"></script>
15
15
  <script src="/opener.js"></script>
16
16
  <script src="/report.js"></script>
17
+ <script src="/normalize.js"></script>
17
18
  <% if (agent === "electron") { %>
18
19
  <link href="/electron.css" rel="stylesheet"/>
19
20
  <% } %>
@@ -205,6 +206,9 @@ body .frame-link.selected {
205
206
  background: rgba(0,0,100,0.06) !important;
206
207
  */
207
208
  }
209
+ .frame-link.selected .del {
210
+ color: white;
211
+ }
208
212
  body.dark .frame-link.selected {
209
213
  background: rgba(0,0,0,0.2) !important;
210
214
  }
@@ -260,6 +264,9 @@ body.dark .grid-btns .btn2 {
260
264
  .tab i {
261
265
  margin-right: 10px;
262
266
  }
267
+ .temp-menu {
268
+ border-top: 1px solid rgba(0,0,0,0.1);
269
+ }
263
270
  .temp-menu .del i {
264
271
  margin: 0;
265
272
  }
@@ -293,6 +300,7 @@ nav .logo {
293
300
  cursor: auto;
294
301
  display: flex;
295
302
  align-items: center;
303
+ width: 100%;
296
304
  }
297
305
  body.dark .header-item.cursor.header-top {
298
306
  background: none !important;
@@ -333,25 +341,23 @@ body.dark #new-tab {
333
341
  <div class='tab'><i class="fa-regular fa-folder-open"></i> Files</div>
334
342
  </a>
335
343
  </div>
336
- <div class='menu'>
344
+ <div class='m h menu' data-type='h'>
337
345
  <% if (config.menu) { %>
338
346
  <%- include('./partials/menu', { menu: config.menu }) %>
339
347
  <% } %>
340
348
  </div>
341
- <div class='temp-menu'>
342
- <%tabs.forEach((tab) => { %>
343
- <a target="<%=tab%>" href="<%=tab%>" class="btn header-item frame-link">
349
+ <div class='m s temp-menu' data-type='s'>
350
+ <%tabs.forEach((tab, i) => { %>
351
+ <a target="s<%=i%>" href="<%=tab%>" class="btn header-item frame-link" data-index="<%=i%>">
344
352
  <div class='tab'>
345
353
  <i class="fa-solid fa-link"></i><div class='display'><%=tab%></div><div class='flexible'></div><button class='btn2 del'><i class="fa-solid fa-xmark"></i></button>
346
354
  </div>
347
355
  </a>
348
356
  <%})%>
349
357
  </div>
350
- <!--
351
358
  <div id='new-tab' class='btn header-item'>
352
359
  <i class="fa-solid fa-plus"></i>
353
360
  </div>
354
- -->
355
361
  <div class='flexible'></div>
356
362
  <div class='grid-btns'>
357
363
  <a class='btn2' href="/?mode=explore" id='explore'><div><i class="fa-solid fa-globe"></i></div><div>Discover</div></a>
@@ -372,7 +378,9 @@ body.dark #new-tab {
372
378
  <button class='btn2 logo' id='forward'><i class="fa-solid fa-arrow-right"></i></button>
373
379
  <button class='btn2 logo' id='reload'><i class="fa-solid fa-rotate-right"></i></button>
374
380
  <div id='location'></div>
375
- <a class='btn2 hidden' id='open-browser' target="_blank" href=""><i class="fa-solid fa-square-arrow-up-right"></i> External Browser</a>
381
+ <div class='flexible'></div>
382
+ <a class='btn2' id='open-browser' target="_blank" href=""><i class="fa-solid fa-square-arrow-up-right"></i> External Browser</a>
383
+ <button class='btn2' id='clone-tab'><i class='fa-regular fa-copy'></i> Clone</button>
376
384
  </div>
377
385
  <!--
378
386
  <div class='flexible'></div>
@@ -398,7 +406,6 @@ let cursorIndex = 0;
398
406
  const getTarget = (href) => {
399
407
  let u
400
408
  if (href.startsWith("http")) {
401
- // u = new URL(href)
402
409
  return href
403
410
  } else if (href.startsWith("/")) {
404
411
  u = new URL("http://localhost" + href)
@@ -420,8 +427,12 @@ const renderCursor = () => {
420
427
  }
421
428
  const nav = (type) => {
422
429
  if (location.hash && location.hash.length > 0) {
423
- let id = getTarget(location.hash.slice(1))
424
- let target = document.querySelector("aside [target='" + id + "']")
430
+ // let id = getTarget(location.hash.slice(1))
431
+ // let target = document.querySelector("aside [target='" + id + "']")
432
+ let id = location.hash.slice(1)
433
+ let type = id[0]
434
+ let index = id.slice(1)
435
+ let target = document.querySelector(`aside .${type} [data-index='${index}']`)
425
436
  let targetFrame = document.querySelector(`iframe[name='${target.target}']`)
426
437
  console.log({ target, targetFrame })
427
438
  if (type === "back") {
@@ -441,8 +452,12 @@ const renderSelection = (e, force) => {
441
452
  if (location.hash && location.hash.length > 0) {
442
453
  console.log("# 2")
443
454
  document.querySelector(".bar").classList.remove("hidden")
444
- let id = getTarget(location.hash.slice(1))
455
+ //let id = getTarget(location.hash.slice(1))
456
+ let id = location.hash.slice(1)
457
+ let type = id[0]
458
+ let index = id.slice(1)
445
459
 
460
+ console.log({ type, index })
446
461
 
447
462
  // select the selected link
448
463
  // document.querySelectorAll(".frame-link").forEach((el) => {
@@ -464,22 +479,32 @@ const renderSelection = (e, force) => {
464
479
 
465
480
  if (!target) {
466
481
  // find target by id
467
- target = document.querySelector("aside [target='" + id + "']")
482
+ //target = document.querySelector("aside [target='" + id + "']")
483
+ target = document.querySelector(`aside .${type} [data-index='${index}']`)
468
484
  }
469
485
 
470
486
  if (target) {
471
487
  console.log("# 3")
472
488
 
473
- //document.querySelector("#location").innerHTML = id
474
- document.querySelector("#location").innerHTML = location.hash.slice(1)
475
489
 
476
- if (location.hash.slice(1).startsWith("/")) {
477
- document.querySelector("#open-browser").classList.add("hidden")
478
- } else {
479
- document.querySelector("#open-browser").classList.remove("hidden")
480
- //document.querySelector("#open-browser").href = id
481
- document.querySelector("#open-browser").href = location.hash.slice(1)
482
- }
490
+ let url = target.href
491
+ document.querySelector("#open-browser").href = url
492
+ document.querySelector("#clone-tab").setAttribute("data-href", url)
493
+ document.querySelector("#location").innerHTML = url
494
+
495
+ // document.querySelector("#location").innerHTML = location.hash.slice(1)
496
+ //
497
+ // if (location.hash.slice(1).startsWith("/")) {
498
+ // //document.querySelector("#open-browser").classList.add("hidden")
499
+ // document.querySelector("#open-browser").classList.remove("hidden")
500
+ // document.querySelector("#open-browser").href = location.hash.slice(1)
501
+ // } else {
502
+ // document.querySelector("#open-browser").classList.remove("hidden")
503
+ // //document.querySelector("#open-browser").href = id
504
+ // document.querySelector("#open-browser").href = location.hash.slice(1)
505
+ // }
506
+ // document.querySelector("#clone-tab").setAttribute("data-href", location.hash.slice(1))
507
+
483
508
 
484
509
 
485
510
  target.classList.add("selected")
@@ -507,9 +532,14 @@ const renderSelection = (e, force) => {
507
532
  console.log("# 5", { src: targetFrame.src, href: target.href, target: target.target })
508
533
  // triggered with a click
509
534
  // do not reload iframe if the href and the src is the same
510
- if (targetFrame.src.includes(target.target)) {
511
- // if (targetFrame.src.includes(target.href)) {
512
- // if (targetFrame.src === target.href) {
535
+ if (target.closest(".h")) {
536
+ // if (targetFrame.src.includes(target.target)) {
537
+ if (targetFrame.src.includes(target.href)) {
538
+ // if (targetFrame.src === target.href) {
539
+ e.preventDefault()
540
+ e.stopPropagation()
541
+ }
542
+ } else if (target.closest(".s")) {
513
543
  e.preventDefault()
514
544
  e.stopPropagation()
515
545
  }
@@ -546,8 +576,12 @@ const renderSelection = (e, force) => {
546
576
  frame.src = target.href
547
577
  frame.setAttribute(
548
578
  "allow",
549
- "clipboard-read; clipboard-write; accelerometer; ambient-light-sensor; autoplay; battery; camera; display-capture; gamepad; geolocation; gyroscope; hid; identity-credentials-get; microphone; midi; otp-credentials; serial;"
579
+ "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
550
580
  )
581
+ frame.contentWindow.prompt = function(a, b) {
582
+ console.log("prompt1", { a, b })
583
+ return prompt(a, b)
584
+ }
551
585
  document.querySelector("main").appendChild(frame)
552
586
  loaded[target.target] = true
553
587
  }
@@ -582,16 +616,33 @@ document.querySelector("#forward").addEventListener("click", (e) => {
582
616
  nav("forward")
583
617
  })
584
618
 
585
- /*
586
619
  document.querySelector("#new-tab").addEventListener("click", async (e) => {
587
- const url = prompt("url")
620
+ const u = prompt("url")
621
+
622
+ let o = {}
623
+ if (/^[0-9.:]/.test(u)) {
624
+ o.forceHttp = true
625
+ } else if (/^localhost/.test(u)) {
626
+ o.forceHttp = true
627
+ } else {
628
+ o.forceHttps = true
629
+ }
630
+ const url = normalizeURL(u, o)
588
631
 
589
632
  if (url && url.trim().length > 0) {
590
633
  console.log("url", url)
591
- await addTab(url)
634
+ const { tab, frame } = await addTab(url)
635
+ tab.click()
636
+ }
637
+ })
638
+ document.querySelector("#clone-tab").addEventListener("click", async (e) => {
639
+ const url = e.target.getAttribute("data-href")
640
+ if (url && url.trim().length > 0) {
641
+ console.log("url", url)
642
+ const { tab, frame } = await addTab(url)
643
+ tab.click()
592
644
  }
593
645
  })
594
- */
595
646
 
596
647
  const syncTabs = async () => {
597
648
  let tabs = []
@@ -615,11 +666,16 @@ const syncTabs = async () => {
615
666
  })
616
667
  }
617
668
  const addTab = async (url) => {
669
+ debugger
618
670
  let item = document.createElement("a")
619
671
  //let id = "ts" + Date.now()
620
- let id = url
672
+ let index = document.querySelectorAll("aside .s .frame-link").length
673
+
674
+ let id = "s" + index
675
+ // let id = url
621
676
  item.target = id
622
677
  item.href = url
678
+ item.setAttribute("data-index", index)
623
679
  item.className = "btn header-item frame-link"
624
680
  item.innerHTML = `<div class='tab'><i class="fa-solid fa-link"></i><div class='display'>${url}</div><div class='flexible'></div><button class='btn2 del'><i class="fa-solid fa-xmark"></i></button></div>`
625
681
 
@@ -627,11 +683,23 @@ const addTab = async (url) => {
627
683
 
628
684
  let frame = document.createElement("iframe")
629
685
  frame.name = id
686
+ frame.setAttribute(
687
+ "allow",
688
+ "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
689
+ )
630
690
  document.querySelector("main").appendChild(frame)
691
+ frame.contentWindow.prompt = function(a, b) {
692
+ console.log("prompt", { a, b })
693
+ return prompt(a, b)
694
+ }
631
695
 
632
696
  await syncTabs()
633
697
 
634
698
 
699
+ return {
700
+ tab: item,
701
+ frame
702
+ }
635
703
 
636
704
  }
637
705
 
@@ -698,7 +766,10 @@ document.querySelector("aside").addEventListener("click", async (e) => {
698
766
  // console.log("ID", id, target.href)
699
767
  // location.hash = id
700
768
 
701
- location.hash = target.getAttribute("href")
769
+ //location.hash = target.getAttribute("href")
770
+ let index = target.getAttribute("data-index")
771
+ let type = target.closest(".m").getAttribute("data-type")
772
+ location.hash = type + index
702
773
 
703
774
  document.querySelectorAll(".frame-link").forEach((el) => {
704
775
  el.classList.remove("selected")
@@ -759,6 +759,11 @@ const reloadMemory = async () => {
759
759
  <div id='save' class='btn disabled'>
760
760
  <span class='save'><i class="fa-solid fa-check"></i> Save</span>
761
761
  </div>
762
+ <% if (proxies) { %>
763
+ <% proxies.forEach((proxy) => { %>
764
+ <a class='btn' target="_blank" href="<%=proxy.proxy%>"><i class='fa-solid fa-up-right-from-square'></i> Proxy <%=proxy.name ? "(" + proxy.name + ")" : proxy.proxy%></a>
765
+ <% }) %>
766
+ <% } %>
762
767
  </div>
763
768
  <% } %>
764
769
  </header>
@@ -739,6 +739,11 @@ const reloadMemory = async () => {
739
739
  <span class='stop hidden'><i class="fa-solid fa-stop"></i> Stop</span>
740
740
  </div>
741
741
  <button class='btn' id='open-fs' data-filepath="<%=filepath%>"><i class="fa-solid fa-eye"></i> View File</button>
742
+ <% if (proxies) { %>
743
+ <% proxies.forEach((proxy) => { %>
744
+ <a class='btn' target="_blank" href="<%=proxy.proxy%>"><i class='fa-solid fa-up-right-from-square'></i> Proxy <%=proxy.name ? "(" + proxy.name + ")" : proxy.proxy%></a>
745
+ <% }) %>
746
+ <% } %>
742
747
  <!--
743
748
  <button class='btn' id='source' data-editor-url="<%=editorUrl%>">Navigate</button>
744
749
  -->
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
  <% } else { %>
12
12
  <% if (item.btn) { %>
13
- <a data-index="<%=index%>" target="<%=item.src%>" data-mode="<%=item.mode%>" href="<%=item.href%>" class='btn header-item frame-link'>
13
+ <a data-index="<%=index%>" target="h<%=index%>" data-mode="<%=item.mode%>" href="<%=item.href%>" class='btn header-item frame-link'>
14
14
  <div class='tab'><%-item.btn%></div>
15
15
  <% if (item.running) { %>
16
16
  <div class='loader'>