pinokiod 7.1.60 → 7.1.62
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 +1 -1
- package/server/index.js +8 -3
- package/server/views/connect/index.ejs +113 -47
- package/server/views/setup.ejs +104 -29
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -11367,7 +11367,8 @@ class Server {
|
|
|
11367
11367
|
name: "x",
|
|
11368
11368
|
title: "x.com",
|
|
11369
11369
|
description: "Connect with X.com",
|
|
11370
|
-
url: "/connect/x"
|
|
11370
|
+
url: "/connect/x",
|
|
11371
|
+
hidden: true
|
|
11371
11372
|
}]
|
|
11372
11373
|
let github_hosts = await this.get_github_hosts()
|
|
11373
11374
|
for(let i=0; i<items.length; i++) {
|
|
@@ -11408,7 +11409,7 @@ class Server {
|
|
|
11408
11409
|
logo: this.logo,
|
|
11409
11410
|
theme: this.theme,
|
|
11410
11411
|
agent: req.agent,
|
|
11411
|
-
items,
|
|
11412
|
+
items: items.filter((item) => !item.hidden),
|
|
11412
11413
|
})
|
|
11413
11414
|
}))
|
|
11414
11415
|
/*
|
|
@@ -15492,6 +15493,7 @@ class Server {
|
|
|
15492
15493
|
if (this.onrestart) {
|
|
15493
15494
|
console.log("onrestart exists")
|
|
15494
15495
|
this.onrestart()
|
|
15496
|
+
res.json({ success: true })
|
|
15495
15497
|
} else {
|
|
15496
15498
|
await this.start({ debug: this.debug, browser: this.browser })
|
|
15497
15499
|
res.json({ success: true })
|
|
@@ -15499,7 +15501,10 @@ class Server {
|
|
|
15499
15501
|
}))
|
|
15500
15502
|
this.app.post("/restart", ex(async (req, res) => {
|
|
15501
15503
|
console.log("post /restart")
|
|
15502
|
-
this.start({ debug: this.debug, browser: this.browser })
|
|
15504
|
+
this.start({ debug: this.debug, browser: this.browser }).catch((error) => {
|
|
15505
|
+
console.error("[Pinokiod] restart failed", error && error.stack ? error.stack : error)
|
|
15506
|
+
})
|
|
15507
|
+
res.json({ success: true })
|
|
15503
15508
|
}))
|
|
15504
15509
|
this.app.post("/network", ex(async (req, res) => {
|
|
15505
15510
|
if (this.kernel.homedir) {
|
|
@@ -44,9 +44,16 @@
|
|
|
44
44
|
color: var(--connect-text);
|
|
45
45
|
margin: 0;
|
|
46
46
|
min-height: 100vh;
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
47
49
|
color-scheme: light;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
body[data-page="connect"][data-agent="electron"] {
|
|
53
|
+
padding-top: 26px;
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
@media (prefers-color-scheme: dark) {
|
|
51
58
|
body[data-page="connect"] {
|
|
52
59
|
--connect-body-bg: #010617;
|
|
@@ -101,6 +108,12 @@
|
|
|
101
108
|
color-scheme: dark;
|
|
102
109
|
}
|
|
103
110
|
|
|
111
|
+
body[data-page="connect"] main.connect-page-shell {
|
|
112
|
+
flex: 1 1 auto;
|
|
113
|
+
padding: 24px 20px 48px;
|
|
114
|
+
box-sizing: border-box;
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
body[data-page="connect"] .container {
|
|
105
118
|
background: var(--connect-card-bg);
|
|
106
119
|
max-width: 600px;
|
|
@@ -261,7 +274,7 @@
|
|
|
261
274
|
body[data-page="connect"] header.head {
|
|
262
275
|
max-width: 600px;
|
|
263
276
|
margin: 0 auto;
|
|
264
|
-
padding: 50px;
|
|
277
|
+
padding: 16px 50px 36px;
|
|
265
278
|
text-align: center;
|
|
266
279
|
}
|
|
267
280
|
|
|
@@ -297,47 +310,23 @@
|
|
|
297
310
|
<script src="/nav.js"></script>
|
|
298
311
|
</head>
|
|
299
312
|
<body class='<%=theme%>' data-agent="<%=agent%>" data-page="connect">
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
<
|
|
304
|
-
<div><i class="fa-solid fa-expand"></i></div>
|
|
305
|
-
</button>
|
|
306
|
-
<button class="btn2" id="back" data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
|
|
307
|
-
<button class="btn2" id="forward" data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
|
|
308
|
-
<button class="btn2" id="refresh-page" data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
|
|
309
|
-
<button class="btn2" id="screenshot" data-tippy-content="screen capture"><i class="fa-solid fa-camera"></i></button>
|
|
310
|
-
<button class="btn2" id="inspector" data-tippy-content="X-ray mode"><i class="fa-solid fa-eye"></i></button>
|
|
311
|
-
<div class="flexible"></div>
|
|
312
|
-
<a class="btn2" href="/columns" data-tippy-content="split into 2 columns">
|
|
313
|
-
<div><i class="fa-solid fa-table-columns"></i></div>
|
|
314
|
-
</a>
|
|
315
|
-
<a class="btn2" href="/rows" data-tippy-content="split into 2 rows">
|
|
316
|
-
<div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
|
|
317
|
-
</a>
|
|
318
|
-
<button class="btn2" id="new-window" data-tippy-content="open a new window" title="open a new window" data-agent="web">
|
|
319
|
-
<div><i class="fa-solid fa-plus"></i></div>
|
|
320
|
-
</button>
|
|
321
|
-
<button class="btn2 hidden" id="close-window" data-tippy-content="close this section">
|
|
322
|
-
<div><i class="fa-solid fa-xmark"></i></div>
|
|
323
|
-
</button>
|
|
324
|
-
</h1>
|
|
325
|
-
</header>
|
|
326
|
-
<header class='head'>
|
|
327
|
-
<div class='logos'>
|
|
328
|
-
<img class='logo' src="/pinokio-black.png"/>
|
|
329
|
-
<div>+</div>
|
|
330
|
-
<% if (name === "huggingface") { %>
|
|
331
|
-
<div class='logo'>🤗</div>
|
|
332
|
-
<% } else if (name === "x") { %>
|
|
333
|
-
<i class="logo fa-brands fa-square-x-twitter"></i>
|
|
334
|
-
<% } else if (name === "pinokio") { %>
|
|
313
|
+
<%- include('../partials/app_navheader', { agent }) %>
|
|
314
|
+
<main class="connect-page-shell">
|
|
315
|
+
<header class='head'>
|
|
316
|
+
<div class='logos'>
|
|
335
317
|
<img class='logo' src="/pinokio-black.png"/>
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
318
|
+
<div>+</div>
|
|
319
|
+
<% if (name === "huggingface") { %>
|
|
320
|
+
<div class='logo'>🤗</div>
|
|
321
|
+
<% } else if (name === "x") { %>
|
|
322
|
+
<i class="logo fa-brands fa-square-x-twitter"></i>
|
|
323
|
+
<% } else if (name === "pinokio") { %>
|
|
324
|
+
<img class='logo' src="/pinokio-black.png"/>
|
|
325
|
+
<% } %>
|
|
326
|
+
</div>
|
|
327
|
+
<h1><%=name%> Connect</h1>
|
|
328
|
+
</header>
|
|
329
|
+
<div class="container">
|
|
341
330
|
<div id="status" class="status hidden">
|
|
342
331
|
</div>
|
|
343
332
|
|
|
@@ -359,7 +348,8 @@
|
|
|
359
348
|
</div>
|
|
360
349
|
</div>
|
|
361
350
|
|
|
362
|
-
|
|
351
|
+
</div>
|
|
352
|
+
</main>
|
|
363
353
|
|
|
364
354
|
<div id="connect-loader" class="loader-overlay hidden">
|
|
365
355
|
<div class="loader-spinner"></div>
|
|
@@ -369,6 +359,7 @@
|
|
|
369
359
|
|
|
370
360
|
<script>
|
|
371
361
|
const CONNECT_NAME = "<%=name%>"
|
|
362
|
+
const CONNECT_AGENT = document.body.getAttribute('data-agent') || 'web'
|
|
372
363
|
const isSecureConnectContext = <%= protocol === "https" ? 'true' : 'false' %>
|
|
373
364
|
const statusElement = document.getElementById('status')
|
|
374
365
|
const loaderElement = document.getElementById('connect-loader')
|
|
@@ -565,6 +556,10 @@
|
|
|
565
556
|
const PROFILE_URL = "<%=config.PROFILE_URL%>"
|
|
566
557
|
const SCOPE = "<%=config.SCOPE%>"
|
|
567
558
|
const name = "<%=name%>"
|
|
559
|
+
const SECURE_CONNECT_POLL_INTERVAL = 2000
|
|
560
|
+
const SECURE_CONNECT_TIMEOUT = 120000
|
|
561
|
+
let secureConnectPollTimer = null
|
|
562
|
+
let secureConnectTimeoutHandle = null
|
|
568
563
|
|
|
569
564
|
function generateRandomString(length) {
|
|
570
565
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
@@ -596,11 +591,24 @@
|
|
|
596
591
|
.replace(/=/g, '');
|
|
597
592
|
}
|
|
598
593
|
|
|
594
|
+
async function openAuthInBrowser(authUrl) {
|
|
595
|
+
const response = await fetch('/go', {
|
|
596
|
+
method: 'POST',
|
|
597
|
+
headers: { 'Content-Type': 'application/json' },
|
|
598
|
+
body: JSON.stringify({ url: authUrl })
|
|
599
|
+
});
|
|
600
|
+
if (!response.ok) {
|
|
601
|
+
throw new Error(`Browser launch failed with status ${response.status}`);
|
|
602
|
+
}
|
|
603
|
+
const payload = await response.json().catch(() => ({}));
|
|
604
|
+
if (payload && payload.error) {
|
|
605
|
+
throw new Error(payload.error);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
599
609
|
// OAuth functions
|
|
600
610
|
async function login() {
|
|
601
611
|
try {
|
|
602
|
-
showLoader('Connecting...');
|
|
603
|
-
setStatus(`Redirecting to ${name}...`, 'warning');
|
|
604
612
|
// Clear existing data
|
|
605
613
|
localStorage.removeItem('oauth_state');
|
|
606
614
|
localStorage.removeItem('code_verifier');
|
|
@@ -626,8 +634,21 @@
|
|
|
626
634
|
});
|
|
627
635
|
|
|
628
636
|
const authUrl = OAUTH_URL + '?' + params.toString();
|
|
629
|
-
|
|
630
|
-
|
|
637
|
+
|
|
638
|
+
if (CONNECT_AGENT === 'electron') {
|
|
639
|
+
startSecureConnectPolling();
|
|
640
|
+
setStatus(`Continue the ${name} login in your browser.`, 'warning');
|
|
641
|
+
try {
|
|
642
|
+
await openAuthInBrowser(authUrl);
|
|
643
|
+
} catch (openError) {
|
|
644
|
+
stopSecureConnectPolling(`Could not open the browser automatically. Please open ${authUrl} manually.`, 'error');
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
showLoader('Connecting...');
|
|
651
|
+
setStatus(`Redirecting to ${name}...`, 'warning');
|
|
631
652
|
window.location.href = authUrl;
|
|
632
653
|
|
|
633
654
|
} catch (error) {
|
|
@@ -637,6 +658,48 @@
|
|
|
637
658
|
}
|
|
638
659
|
}
|
|
639
660
|
|
|
661
|
+
function stopSecureConnectPolling(message = '', type = 'warning') {
|
|
662
|
+
if (secureConnectPollTimer) {
|
|
663
|
+
clearInterval(secureConnectPollTimer);
|
|
664
|
+
secureConnectPollTimer = null;
|
|
665
|
+
}
|
|
666
|
+
if (secureConnectTimeoutHandle) {
|
|
667
|
+
clearTimeout(secureConnectTimeoutHandle);
|
|
668
|
+
secureConnectTimeoutHandle = null;
|
|
669
|
+
}
|
|
670
|
+
if (message) {
|
|
671
|
+
hideLoader();
|
|
672
|
+
setStatus(message, type);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function pollSecureConnectStatus() {
|
|
677
|
+
const token = await ensureValidToken();
|
|
678
|
+
if (!token) {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
stopSecureConnectPolling();
|
|
682
|
+
localStorage.removeItem('oauth_state');
|
|
683
|
+
localStorage.removeItem('code_verifier');
|
|
684
|
+
await fetchUserInfo(token);
|
|
685
|
+
setStatus('Connected.', 'success');
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function startSecureConnectPolling() {
|
|
690
|
+
stopSecureConnectPolling();
|
|
691
|
+
showLoader('Waiting for authentication in the browser...', {
|
|
692
|
+
cancellable: true,
|
|
693
|
+
onCancel: () => stopSecureConnectPolling('Connection cancelled.', 'warning')
|
|
694
|
+
});
|
|
695
|
+
setStatus(`Waiting for ${name} authentication to finish...`, 'warning');
|
|
696
|
+
pollSecureConnectStatus();
|
|
697
|
+
secureConnectPollTimer = setInterval(pollSecureConnectStatus, SECURE_CONNECT_POLL_INTERVAL);
|
|
698
|
+
secureConnectTimeoutHandle = setTimeout(() => {
|
|
699
|
+
stopSecureConnectPolling('Timed out waiting for the browser login to finish. Please try again.', 'error');
|
|
700
|
+
}, SECURE_CONNECT_TIMEOUT);
|
|
701
|
+
}
|
|
702
|
+
|
|
640
703
|
async function handleOAuthCallback(code) {
|
|
641
704
|
showLoader('Finishing connection...');
|
|
642
705
|
try {
|
|
@@ -670,6 +733,7 @@
|
|
|
670
733
|
console.log('Token response:', response)
|
|
671
734
|
// Store tokens
|
|
672
735
|
if (response.access_token) {
|
|
736
|
+
stopSecureConnectPolling();
|
|
673
737
|
// Cleanup
|
|
674
738
|
localStorage.removeItem('oauth_state');
|
|
675
739
|
localStorage.removeItem('code_verifier');
|
|
@@ -721,11 +785,13 @@
|
|
|
721
785
|
await handleOAuthCallback(code);
|
|
722
786
|
return;
|
|
723
787
|
}
|
|
724
|
-
|
|
788
|
+
|
|
725
789
|
// Check existing session with automatic refresh
|
|
726
790
|
const token = await ensureValidToken();
|
|
727
791
|
if (token) {
|
|
728
792
|
await fetchUserInfo(token);
|
|
793
|
+
} else if (CONNECT_AGENT === 'electron' && localStorage.getItem('oauth_state')) {
|
|
794
|
+
startSecureConnectPolling();
|
|
729
795
|
} else {
|
|
730
796
|
setStatus('Not authenticated', 'warning');
|
|
731
797
|
}
|
package/server/views/setup.ejs
CHANGED
|
@@ -240,38 +240,113 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
240
240
|
location.href = location.href
|
|
241
241
|
<% } %>
|
|
242
242
|
<% if (!requirements_pending && !install_required && wait === "caddy") { %>
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
const waitRoot = document.querySelector(".requirements .content")
|
|
244
|
+
const waitLabel = document.querySelector(".requirements .content .loading span")
|
|
245
|
+
const WAIT_TIMEOUT_MS = 120000
|
|
246
|
+
const setWaitLabel = (message) => {
|
|
247
|
+
if (waitLabel) {
|
|
248
|
+
waitLabel.textContent = message
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const showWaitError = (message) => {
|
|
252
|
+
if (waitRoot) {
|
|
253
|
+
waitRoot.innerHTML = `
|
|
254
|
+
<div class='loading'>
|
|
255
|
+
<i class="fa-solid fa-circle-exclamation"></i>
|
|
256
|
+
<span>${message}</span>
|
|
257
|
+
</div>
|
|
258
|
+
<div class='item'>
|
|
259
|
+
<a class='btn' href='${location.href}'>Retry</a>
|
|
260
|
+
</div>
|
|
261
|
+
`
|
|
262
|
+
} else {
|
|
263
|
+
alert(message)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
245
266
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
267
|
+
try {
|
|
268
|
+
setWaitLabel("Enabling secure localhost access...")
|
|
269
|
+
const networkResponse = await fetch("/network", {
|
|
270
|
+
method: "post",
|
|
271
|
+
headers: { "Content-Type": "application/json" },
|
|
272
|
+
body: JSON.stringify({
|
|
273
|
+
PINOKIO_NETWORK_ACTIVE: "1",
|
|
274
|
+
PINOKIO_HTTPS_ACTIVE: "1",
|
|
275
|
+
})
|
|
276
|
+
}).then((res) => {
|
|
277
|
+
return res.json()
|
|
252
278
|
})
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
await new Promise((resolve, reject) => {
|
|
262
|
-
let interval = setInterval(() => {
|
|
263
|
-
fetch("/check_router_up").then((res) => {
|
|
264
|
-
return res.json()
|
|
265
|
-
}).then((res) => {
|
|
266
|
-
console.log(res)
|
|
267
|
-
if (res.success) {
|
|
268
|
-
clearInterval(interval)
|
|
269
|
-
resolve()
|
|
270
|
-
}
|
|
279
|
+
if (networkResponse && networkResponse.error) {
|
|
280
|
+
throw new Error(networkResponse.error)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
setWaitLabel("Restarting Pinokio...")
|
|
284
|
+
try {
|
|
285
|
+
await fetch("/restart", {
|
|
286
|
+
method: "post"
|
|
271
287
|
})
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.warn("restart request ended early", error)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const startedAt = Date.now()
|
|
293
|
+
let lastKnownError = null
|
|
294
|
+
let lastKnownPhase = null
|
|
295
|
+
|
|
296
|
+
await new Promise((resolve, reject) => {
|
|
297
|
+
const interval = setInterval(async () => {
|
|
298
|
+
try {
|
|
299
|
+
const [routerStatus, startupStatus] = await Promise.all([
|
|
300
|
+
fetch("/check_router_up").then((res) => res.json()),
|
|
301
|
+
fetch("/pinokio/startup_status").then((res) => res.json()).catch(() => null)
|
|
302
|
+
])
|
|
303
|
+
|
|
304
|
+
if (routerStatus && routerStatus.success) {
|
|
305
|
+
clearInterval(interval)
|
|
306
|
+
resolve()
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (routerStatus && routerStatus.error) {
|
|
311
|
+
lastKnownError = routerStatus.error
|
|
312
|
+
}
|
|
313
|
+
if (startupStatus && startupStatus.error) {
|
|
314
|
+
lastKnownError = startupStatus.error
|
|
315
|
+
}
|
|
316
|
+
if (startupStatus && startupStatus.phase) {
|
|
317
|
+
lastKnownPhase = startupStatus.phase
|
|
318
|
+
const phaseMessage = startupStatus.phase === "ready"
|
|
319
|
+
? "Secure router is almost ready..."
|
|
320
|
+
: `Restarting Pinokio... (${startupStatus.phase.replace(/_/g, " ")})`
|
|
321
|
+
setWaitLabel(phaseMessage)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (startupStatus && startupStatus.phase === "error") {
|
|
325
|
+
clearInterval(interval)
|
|
326
|
+
reject(new Error(lastKnownError || "Pinokio failed to restart cleanly"))
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if ((Date.now() - startedAt) >= WAIT_TIMEOUT_MS) {
|
|
331
|
+
clearInterval(interval)
|
|
332
|
+
const detail = lastKnownError || lastKnownPhase || "secure localhost router did not come up"
|
|
333
|
+
reject(new Error(`Timed out waiting for restart: ${detail}`))
|
|
334
|
+
}
|
|
335
|
+
} catch (error) {
|
|
336
|
+
lastKnownError = error && error.message ? error.message : String(error)
|
|
337
|
+
if ((Date.now() - startedAt) >= WAIT_TIMEOUT_MS) {
|
|
338
|
+
clearInterval(interval)
|
|
339
|
+
reject(new Error(`Timed out waiting for restart: ${lastKnownError}`))
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}, 1000)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
location.href = "<%=current%>"
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error("secure router setup failed", error)
|
|
348
|
+
showWaitError(error && error.message ? error.message : "Failed to restart Pinokio")
|
|
349
|
+
}
|
|
275
350
|
<% } %>
|
|
276
351
|
if (document.querySelector("#del-bin")) {
|
|
277
352
|
document.querySelector("#del-bin").addEventListener("click", async (e) => {
|