diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-03 06:00:40 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-03 06:00:40 +0200 |
| commit | 7a3c8e74ecbf28d7921433430023f4ccaf8426dc (patch) | |
| tree | c16ae627d307990cd049a6113ab2685826c29733 /modules/ext/webdav.js | |
| parent | aafb0fbde5289a45f9f818a00141f80b149be283 (diff) | |
auto: 2026-06-03_06:00 changes
Diffstat (limited to 'modules/ext/webdav.js')
| -rw-r--r-- | modules/ext/webdav.js | 149 |
1 files changed, 106 insertions, 43 deletions
diff --git a/modules/ext/webdav.js b/modules/ext/webdav.js index eea513b..01856ce 100644 --- a/modules/ext/webdav.js +++ b/modules/ext/webdav.js @@ -24,6 +24,27 @@ class WebDAVModule { return ''; } + _detectSessionUser() { + try { + const cookies = document.cookie.split(';').map(c => c.trim()); + for (const c of cookies) { + if (c.startsWith('webx11_user=')) { + return decodeURIComponent(c.substring('webx11_user='.length)); + } + } + } catch (e) {} + return null; + } + + _promptForToken(spaceName) { + // Use textarea+execCommand for mobile WebView compatibility + const input = window.prompt('Enter your WebDAV token for space "' + spaceName + '":', ''); + if (input && input.trim()) { + return input.trim(); + } + return null; + } + requirements() { const cfg = localStorage.getItem('webx11_webdav_config'); if (!cfg) return { met: false, missing: ['WebDAV endpoint not configured. Use webdav-config to set it up.'] }; @@ -59,24 +80,33 @@ class WebDAVModule { const saved = localStorage.getItem(this._STORAGE_KEY); if (saved) { this.config = JSON.parse(saved); - // Try to get password from SecretVault + // Try to get password from SecretVault (async — will be resolved on first use) if (window.webx11_secretVault && this.config && this.config.username) { - let encryptedPass = window.webx11_secretVault.decrypt('webdav_pass_' + this.config.username); - if (encryptedPass !== null && encryptedPass !== undefined) { - this.config.password = encryptedPass; - } else { - // Fallback to old token key - encryptedPass = window.webx11_secretVault.decrypt('webdav_token_' + this.config.username); - if (encryptedPass !== null && encryptedPass !== undefined) { - this.config.password = encryptedPass; - } - } + // Store a placeholder; actual decrypt happens on first auth call + this._cachedPassUser = this.config.username; + } + } + } catch (e) { /* ignore */ } + } + + async _resolvePassword() { + if (!window.webx11_secretVault || !this._cachedPassUser) return; + try { + const pass = await window.webx11_secretVault.decrypt('webdav_pass_' + this._cachedPassUser); + if (pass !== null && typeof pass === 'string') { + this.config.password = pass; + } else { + // Fallback to old token key + const tokenPass = await window.webx11_secretVault.decrypt('webdav_token_' + this._cachedPassUser); + if (tokenPass !== null && typeof tokenPass === 'string') { + this.config.password = tokenPass; } } } catch (e) { /* ignore */ } + this._cachedPassUser = null; } - _saveConfig() { + async _saveConfig() { try { if (this.config) { const toStore = { @@ -85,14 +115,13 @@ class WebDAVModule { }; // Try SecretVault for encrypted password storage if (window.webx11_secretVault && this.config.password) { - window.webx11_secretVault.encrypt('webdav_pass_' + this.config.username, this.config.password); + await window.webx11_secretVault.encrypt('webdav_pass_' + this.config.username, this.config.password); // Also store space token if (this._spaceName) { - window.webx11_secretVault.encrypt('webdav_token_' + this._spaceName, this.config.password); + await window.webx11_secretVault.encrypt('webdav_token_' + this._spaceName, this.config.password); } } - // Also store in localStorage for backward compat (SecretVault decrypts later) - // Store only url and username hint in plaintext lS, encrypted password handled above + // Also store in localStorage for backward compat (url and username only) localStorage.setItem(this._STORAGE_KEY, JSON.stringify(toStore)); } else { localStorage.removeItem(this._STORAGE_KEY); @@ -110,6 +139,10 @@ class WebDAVModule { if (!this.config || !this.config.url) { throw new Error('WebDAV not configured. Use webdav-config first.'); } + // Resolve password from SecretVault if needed (lazy load) + if (this._cachedPassUser) { + await this._resolvePassword(); + } const baseUrl = this.config.url.replace(/\/+$/, ''); const urlPath = path ? (path.startsWith('/') ? path : '/' + path) : '/'; const url = baseUrl + urlPath; @@ -372,7 +405,12 @@ class WebDAVModule { // Check SecretVault first if (window.webx11_secretVault) { - token = window.webx11_secretVault.decrypt('webdav_token_' + spaceName); + const vaultToken = window.webx11_secretVault.decrypt('webdav_token_' + spaceName); + if (vaultToken && typeof vaultToken.then === 'function') { + token = await vaultToken; + } else if (vaultToken !== null && vaultToken !== undefined) { + token = vaultToken; + } } // Fallback to localStorage @@ -380,18 +418,30 @@ class WebDAVModule { token = localStorage.getItem('webx11_spaces_' + spaceName); } - // Fallback: fetch .token directly from server + // If still no token, use session-aware prompting if (!token) { - try { - const tokenResp = await fetch('/spaces/' + spaceName + '/.token'); - if (tokenResp.ok) { - token = (await tokenResp.text()).trim(); - // Cache in SecretVault for future use - if (window.webx11_secretVault) { - window.webx11_secretVault.encrypt('webdav_token_' + spaceName, token); - } - } - } catch (e) { /* .token not accessible */ } + const sessionUser = self._detectSessionUser(); + if (sessionUser && sessionUser === spaceName) { + self.terminal.print('👋 Welcome back, ' + sessionUser + '!', 'success'); + self.terminal.print('No saved WebDAV config found. Enter your token:', 'info'); + } else if (sessionUser) { + self.terminal.print('👋 You are logged in as "' + sessionUser + '" — this space is "' + spaceName + '".', 'info'); + self.terminal.print('Enter the WebDAV token for space "' + spaceName + '":', 'info'); + } else { + self.terminal.print('No saved WebDAV config found for space "' + spaceName + '".', 'info'); + self.terminal.print('Enter the WebDAV token (get it from the WebX11 dashboard):', 'info'); + } + self.terminal.print('', 'text-muted'); + self.terminal.print(' webdav-config ' + autoUrl + ' ' + spaceName + ' <your-token>', 'success'); + self.terminal.print('', 'text-muted'); + // Pre-configure URL + username so user only needs token + self.config = { + url: autoUrl, + username: spaceName, + password: '' + }; + await self._saveConfig(); + return; } if (token) { @@ -400,7 +450,7 @@ class WebDAVModule { username: spaceName, password: token }; - self._saveConfig(); + await self._saveConfig(); self._autoConfigured = true; self.terminal.print('Auto-configured WebDAV for space: ' + spaceName, 'success'); try { @@ -413,7 +463,7 @@ class WebDAVModule { } } else { self.terminal.print('No WebDAV token found for space: ' + spaceName, 'warning'); - self.terminal.print('Get your token from /spaces/' + spaceName + '/.token or the dashboard.', 'info'); + self.terminal.print('Get your token from the WebX11 dashboard.', 'info'); self.terminal.print('Auto-setting URL: ' + autoUrl, 'info'); self.terminal.print('Then configure with: webdav-config ' + autoUrl + ' ' + spaceName + ' <your-token>', 'info'); // At minimum set the URL @@ -422,7 +472,7 @@ class WebDAVModule { username: spaceName, password: '' }; - self._saveConfig(); + await self._saveConfig(); self._autoConfigured = true; } return; @@ -434,7 +484,7 @@ class WebDAVModule { username: args[1] || (self.config ? self.config.username : ''), password: args[2] || (self.config ? self.config.password : '') }; - self._saveConfig(); + await self._saveConfig(); self.terminal.print('Testing WebDAV connection...', 'info'); try { const resp = await self._webdavRequest('OPTIONS', '/'); @@ -447,11 +497,15 @@ class WebDAVModule { }; // --- webdav-status: show current WebDAV config --- - this.commands['webdav-status'] = () => { + this.commands['webdav-status'] = async () => { if (!self.config || !self.config.url) { self.terminal.print('WebDAV not configured. Use webdav-config <url> [username] [password]', 'warning'); return; } + // Resolve password from SecretVault if needed + if (self._cachedPassUser) { + await self._resolvePassword(); + } self.terminal.print('WebDAV Status:', 'text-muted'); self.terminal.print(' URL: ' + self.config.url); self.terminal.print(' User: ' + (self.config.username || '(none)')); @@ -1197,24 +1251,31 @@ class WebDAVModule { async previewWebdavFile(entry) { try { - var resp = await this._webdavRequest('GET', entry.href); + var basePath = (this._browsingPath || '/').replace(/\/+$/, ''); + var fullPath = basePath + '/' + entry.name; + var resp = await this._webdavRequest('GET', fullPath); if (!resp.ok) { this.terminal.print('Failed to fetch: HTTP ' + resp.status, 'error'); return; } - var content = await resp.text(); var isHtml = entry.name.match(/\.html?$/i); var isImage = entry.name.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i); - var size = content.length; + var size = parseInt(resp.headers.get('content-length') || '0'); if (size > 512000 && !isHtml && !isImage) { // Large non-html file — offer download instead this.terminal.print(entry.name + ' is ' + this._formatSize(size) + ' — double-click to download instead of preview.', 'warning'); + var textPreview = await resp.text(); this.terminal.print('--- ' + entry.name + ' (first 2000 chars) ---'); - this.terminal.print(content.substring(0, 2000)); - if (content.length > 2000) this.terminal.print('...(truncated ' + this._formatSize(size) + ' total)'); + this.terminal.print(textPreview.substring(0, 2000)); + if (textPreview.length > 2000) this.terminal.print('...(truncated ' + this._formatSize(size) + ' total)'); return; } if (isImage) { - var dataUrl = 'data:image/' + (entry.name.split('.').pop()) + ';base64,' + btoa(unescape(encodeURIComponent(content))); + var blob = await resp.blob(); + var dataUrl = await new Promise(function(resolve) { + var reader = new FileReader(); + reader.onloadend = function() { resolve(reader.result); }; + reader.readAsDataURL(blob); + }); // Use preview modal var previewTitle = document.getElementById('preview-title'); var previewIframe = document.getElementById('preview-iframe'); @@ -1227,6 +1288,7 @@ class WebDAVModule { } if (isHtml) { + var content = await resp.text(); var previewTitle = document.getElementById('preview-title'); var previewIframe = document.getElementById('preview-iframe'); if (previewTitle && previewIframe) { @@ -1238,7 +1300,8 @@ class WebDAVModule { } // Text file — print to terminal - this.terminal.print('--- ' + entry.name + ' (' + this._formatSize(size) + ') ---', 'info'); + var content = await resp.text(); + this.terminal.print('--- ' + entry.name + ' (' + this._formatSize(content.length) + ') ---', 'info'); this.terminal.print(content); this.terminal.print('--- End: ' + entry.name + ' ---', 'text-muted'); } catch (e) { @@ -1286,8 +1349,8 @@ class WebDAVModule { var fullPath = basePath + '/' + entry.name; var resp = await this._webdavRequest('GET', fullPath); if (!resp.ok) { this.terminal.print('Download failed: HTTP ' + resp.status, 'error'); return; } - var content = await resp.text(); - var blob = new Blob([content], { type: 'application/octet-stream' }); + // Use blob() instead of text() to preserve binary data + var blob = await resp.blob(); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; @@ -1299,7 +1362,7 @@ class WebDAVModule { document.body.removeChild(a); URL.revokeObjectURL(url); }, 200); - this.terminal.print('⬇ Downloaded: ' + entry.name + ' (' + this._formatSize(content.length) + ')', 'success'); + this.terminal.print('⬇ Downloaded: ' + entry.name + ' (' + this._formatSize(blob.size) + ')', 'success'); } catch (e) { this.terminal.print('Download error: ' + e.message, 'error'); } |