pinokiod 1.0.17 → 1.0.18

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.18",
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,7 +576,7 @@ 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
  )
551
581
  document.querySelector("main").appendChild(frame)
552
582
  loaded[target.target] = true
@@ -582,16 +612,33 @@ document.querySelector("#forward").addEventListener("click", (e) => {
582
612
  nav("forward")
583
613
  })
584
614
 
585
- /*
586
615
  document.querySelector("#new-tab").addEventListener("click", async (e) => {
587
- const url = prompt("url")
616
+ const u = prompt("url")
617
+
618
+ let o = {}
619
+ if (/^[0-9.:]/.test(u)) {
620
+ o.forceHttp = true
621
+ } else if (/^localhost/.test(u)) {
622
+ o.forceHttp = true
623
+ } else {
624
+ o.forceHttps = true
625
+ }
626
+ const url = normalizeURL(u, o)
588
627
 
589
628
  if (url && url.trim().length > 0) {
590
629
  console.log("url", url)
591
- await addTab(url)
630
+ const { tab, frame } = await addTab(url)
631
+ tab.click()
632
+ }
633
+ })
634
+ document.querySelector("#clone-tab").addEventListener("click", async (e) => {
635
+ const url = e.target.getAttribute("data-href")
636
+ if (url && url.trim().length > 0) {
637
+ console.log("url", url)
638
+ const { tab, frame } = await addTab(url)
639
+ tab.click()
592
640
  }
593
641
  })
594
- */
595
642
 
596
643
  const syncTabs = async () => {
597
644
  let tabs = []
@@ -615,11 +662,16 @@ const syncTabs = async () => {
615
662
  })
616
663
  }
617
664
  const addTab = async (url) => {
665
+ debugger
618
666
  let item = document.createElement("a")
619
667
  //let id = "ts" + Date.now()
620
- let id = url
668
+ let index = document.querySelectorAll("aside .s .frame-link").length
669
+
670
+ let id = "s" + index
671
+ // let id = url
621
672
  item.target = id
622
673
  item.href = url
674
+ item.setAttribute("data-index", index)
623
675
  item.className = "btn header-item frame-link"
624
676
  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
677
 
@@ -627,11 +679,19 @@ const addTab = async (url) => {
627
679
 
628
680
  let frame = document.createElement("iframe")
629
681
  frame.name = id
682
+ frame.setAttribute(
683
+ "allow",
684
+ "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 *;"
685
+ )
630
686
  document.querySelector("main").appendChild(frame)
631
687
 
632
688
  await syncTabs()
633
689
 
634
690
 
691
+ return {
692
+ tab: item,
693
+ frame
694
+ }
635
695
 
636
696
  }
637
697
 
@@ -698,7 +758,10 @@ document.querySelector("aside").addEventListener("click", async (e) => {
698
758
  // console.log("ID", id, target.href)
699
759
  // location.hash = id
700
760
 
701
- location.hash = target.getAttribute("href")
761
+ //location.hash = target.getAttribute("href")
762
+ let index = target.getAttribute("data-index")
763
+ let type = target.closest(".m").getAttribute("data-type")
764
+ location.hash = type + index
702
765
 
703
766
  document.querySelectorAll(".frame-link").forEach((el) => {
704
767
  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'>