Refactor cookie handling and UI interactions
This commit is contained in:
+211
-42
@@ -1,8 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Sign Up / Login (FS)</title>
|
<title>Sign Up / Login (FS)</title>
|
||||||
<script src="https://js.puter.com/v2/"></script>
|
<script src="https://js.puter.com/v2/"></script>
|
||||||
<style>
|
<style>
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
.file-actions { display:flex; gap:8px; margin-top:8px; flex-wrap:wrap; }
|
.file-actions { display:flex; gap:8px; margin-top:8px; flex-wrap:wrap; }
|
||||||
.file-item { background:#2b2b2b; padding:8px; border-radius:6px; display:flex; gap:8px; align-items:center; }
|
.file-item { background:#2b2b2b; padding:8px; border-radius:6px; display:flex; gap:8px; align-items:center; }
|
||||||
.small-btn { padding:6px 8px; font-size:0.9em; width:auto; }
|
.small-btn { padding:6px 8px; font-size:0.9em; width:auto; }
|
||||||
|
.disabled { opacity: 0.5; pointer-events: none; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -44,7 +45,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Helper to get all cookies as an object
|
// Small, robust FS helpers that adapt to a few runtime variations of 'puter.fs' APIs.
|
||||||
|
const msg = document.getElementById('msg');
|
||||||
|
const filesListEl = document.getElementById('filesList');
|
||||||
|
const fileActionsEl = document.getElementById('fileActions');
|
||||||
|
|
||||||
|
const DIR = 'cookies';
|
||||||
|
|
||||||
|
function disableUI(reason) {
|
||||||
|
['ensure','signup','refresh','clearAll'].forEach(id => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) {
|
||||||
|
el.classList.add('disabled');
|
||||||
|
el.title = reason || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function enableUI() {
|
||||||
|
['ensure','signup','refresh','clearAll'].forEach(id => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.classList.remove('disabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic cookie helpers
|
||||||
function getCookies() {
|
function getCookies() {
|
||||||
if (!document.cookie) return {};
|
if (!document.cookie) return {};
|
||||||
return Object.fromEntries(document.cookie.split('; ').filter(Boolean).map(c => {
|
return Object.fromEntries(document.cookie.split('; ').filter(Boolean).map(c => {
|
||||||
@@ -53,35 +77,117 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to set cookies from object
|
|
||||||
function setCookies(cookies) {
|
function setCookies(cookies) {
|
||||||
Object.entries(cookies).forEach(([k, v]) => {
|
Object.entries(cookies).forEach(([k, v]) => {
|
||||||
// Set cookie with path=/ so it applies site-wide; optionally add expiry if needed
|
const safeVal = typeof v === 'string' ? v : JSON.stringify(v);
|
||||||
document.cookie = `${k}=${v}; path=/;`;
|
// encode cookie value to be safe
|
||||||
|
document.cookie = `${k}=${encodeURIComponent(safeVal)}; path=/;`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = document.getElementById('msg');
|
// Detect puter and wrap file ops so the rest of the code stays clean.
|
||||||
const filesListEl = document.getElementById('filesList');
|
function hasPuter() {
|
||||||
const fileActionsEl = document.getElementById('fileActions');
|
return typeof window.puter !== 'undefined' && !!puter.fs;
|
||||||
|
}
|
||||||
|
|
||||||
const DIR = 'cookies';
|
async function fsMkdir(dir) {
|
||||||
|
if (!hasPuter()) throw new Error('puter not available');
|
||||||
|
// try several possible mkdir signatures
|
||||||
|
if (puter.fs.mkdir) {
|
||||||
|
try { return await puter.fs.mkdir(dir); } catch (e) {
|
||||||
|
// some implementations accept options or throw when exists - try readdir to confirm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallback: try readdir to "ensure" existence
|
||||||
|
try { await puter.fs.readdir(dir); return true; } catch (e) { throw e; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fsReaddir(dir) {
|
||||||
|
if (!hasPuter()) throw new Error('puter not available');
|
||||||
|
if (!puter.fs.readdir) throw new Error('puter.fs.readdir not available');
|
||||||
|
const res = await puter.fs.readdir(dir);
|
||||||
|
// Normalize many possible shapes:
|
||||||
|
// - array of strings ['a','b']
|
||||||
|
// - array of objects [{name:'a'},{name:'b'}]
|
||||||
|
// - object with entries property { entries: [...] }
|
||||||
|
if (!res) return [];
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
if (res.length === 0) return [];
|
||||||
|
if (typeof res[0] === 'string') return res;
|
||||||
|
if (res[0] && typeof res[0] === 'object' && 'name' in res[0]) return res.map(r => r.name);
|
||||||
|
// else try to stringify/return something readable
|
||||||
|
return res.map(String);
|
||||||
|
}
|
||||||
|
if (res.entries && Array.isArray(res.entries)) {
|
||||||
|
return res.entries.map(e => (typeof e === 'string' ? e : (e.name || String(e))));
|
||||||
|
}
|
||||||
|
// unknown format, try to coerce
|
||||||
|
return [String(res)];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fsWrite(path, data) {
|
||||||
|
if (!hasPuter()) throw new Error('puter not available');
|
||||||
|
// try write(path, data)
|
||||||
|
if (puter.fs.write) return await puter.fs.write(path, data);
|
||||||
|
// try writeFile
|
||||||
|
if (puter.fs.writeFile) return await puter.fs.writeFile(path, data);
|
||||||
|
// try put
|
||||||
|
if (puter.fs.put) return await puter.fs.put(path, data);
|
||||||
|
throw new Error('No supported write method on puter.fs');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fsRead(path) {
|
||||||
|
if (!hasPuter()) throw new Error('puter not available');
|
||||||
|
if (puter.fs.read) {
|
||||||
|
const r = await puter.fs.read(path);
|
||||||
|
// many implementations return string, Buffer, or object { data: '...' }
|
||||||
|
if (typeof r === 'string') return r;
|
||||||
|
if (r == null) return r;
|
||||||
|
if (typeof r === 'object') {
|
||||||
|
if ('data' in r) return r.data;
|
||||||
|
if ('content' in r) return r.content;
|
||||||
|
// fallback to JSON string
|
||||||
|
try { return JSON.stringify(r); } catch (e) { return String(r); }
|
||||||
|
}
|
||||||
|
return String(r);
|
||||||
|
}
|
||||||
|
if (puter.fs.readFile) {
|
||||||
|
const r = await puter.fs.readFile(path);
|
||||||
|
if (typeof r === 'string') return r;
|
||||||
|
if (r && r.toString) return r.toString();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
throw new Error('No supported read method on puter.fs');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fsDelete(path) {
|
||||||
|
if (!hasPuter()) throw new Error('puter not available');
|
||||||
|
// try multiple names
|
||||||
|
if (puter.fs.delete) return await puter.fs.delete(path);
|
||||||
|
if (puter.fs.unlink) return await puter.fs.unlink(path);
|
||||||
|
if (puter.fs.rm) return await puter.fs.rm(path);
|
||||||
|
throw new Error('No supported delete method on puter.fs');
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI / Actions
|
||||||
async function ensureDir() {
|
async function ensureDir() {
|
||||||
|
msg.textContent = 'Ensuring cookies directory...';
|
||||||
|
if (!hasPuter()) {
|
||||||
|
msg.textContent = 'puter runtime not available in this environment.';
|
||||||
|
disableUI('puter not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// Try creating the directory; if it exists, some implementations may throw - handle gracefully
|
await fsMkdir(DIR);
|
||||||
await puter.fs.mkdir(DIR);
|
|
||||||
msg.textContent = `Ensured directory: ${DIR}`;
|
msg.textContent = `Ensured directory: ${DIR}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If the error indicates directory exists, ignore
|
// try readdir to check if exists
|
||||||
// Otherwise, show message
|
|
||||||
// Many runtime implementations return an error - just attempt readdir to confirm existence
|
|
||||||
try {
|
try {
|
||||||
await puter.fs.readdir(DIR);
|
await fsReaddir(DIR);
|
||||||
msg.textContent = `Directory already exists: ${DIR}`;
|
msg.textContent = `Directory already exists: ${DIR}`;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
msg.textContent = 'Error ensuring directory: ' + (err.message || err);
|
msg.textContent = 'Error ensuring directory: ' + (err.message || err);
|
||||||
throw err;
|
console.error('ensureDir ->', e, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,16 +195,20 @@
|
|||||||
async function listFiles() {
|
async function listFiles() {
|
||||||
filesListEl.textContent = 'Loading...';
|
filesListEl.textContent = 'Loading...';
|
||||||
fileActionsEl.innerHTML = '';
|
fileActionsEl.innerHTML = '';
|
||||||
|
if (!hasPuter()) {
|
||||||
|
filesListEl.textContent = 'puter runtime not available. File listing is disabled.';
|
||||||
|
disableUI('puter not available');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const entries = await puter.fs.readdir(DIR).catch(err => {
|
const entries = await fsReaddir(DIR).catch(err => {
|
||||||
// If directory doesn't exist, show empty list
|
// if directory doesn't exist, show empty list
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
if (!entries || entries.length === 0) {
|
if (!entries || entries.length === 0) {
|
||||||
filesListEl.textContent = 'No saved cookie files.';
|
filesListEl.textContent = 'No saved cookie files.';
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
// entries expected to be an array of filenames
|
|
||||||
filesListEl.innerHTML = '';
|
filesListEl.innerHTML = '';
|
||||||
entries.forEach(name => {
|
entries.forEach(name => {
|
||||||
const wrap = document.createElement('div');
|
const wrap = document.createElement('div');
|
||||||
@@ -126,78 +236,128 @@
|
|||||||
return entries;
|
return entries;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
filesListEl.textContent = 'Error listing files: ' + (e.message || e);
|
filesListEl.textContent = 'Error listing files: ' + (e.message || e);
|
||||||
|
console.error('listFiles error', e);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveCookiesToFile() {
|
async function saveCookiesToFile() {
|
||||||
msg.textContent = 'Signing in and saving cookies to file...';
|
msg.textContent = 'Signing in and saving cookies to file...';
|
||||||
|
if (!hasPuter()) {
|
||||||
|
msg.textContent = 'puter runtime not available.';
|
||||||
|
disableUI('puter not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
// ensure sign-in works
|
||||||
|
if (!puter.auth || !puter.auth.signIn) {
|
||||||
|
throw new Error('puter.auth.signIn not available');
|
||||||
|
}
|
||||||
const user = await puter.auth.signIn();
|
const user = await puter.auth.signIn();
|
||||||
if (!user) throw new Error('Sign in failed.');
|
if (!user) throw new Error('Sign in failed or cancelled.');
|
||||||
// ensure dir exists
|
|
||||||
await ensureDir();
|
await ensureDir();
|
||||||
const cookies = getCookies();
|
const cookies = getCookies();
|
||||||
const filename = `${DIR}/cookies-${Date.now()}.txt`;
|
const filename = `cookies-${Date.now()}.txt`;
|
||||||
await puter.fs.write(filename, JSON.stringify(cookies));
|
const path = `${DIR}/${filename}`;
|
||||||
msg.textContent = `Saved cookies to ${filename}`;
|
await fsWrite(path, JSON.stringify(cookies, null, 2));
|
||||||
|
msg.textContent = `Saved cookies to ${path}`;
|
||||||
await listFiles();
|
await listFiles();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
msg.textContent = 'Error saving file: ' + (e.message || e);
|
msg.textContent = 'Error saving file: ' + (e.message || e);
|
||||||
|
console.error('saveCookiesToFile error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadFile(filename) {
|
async function loadFile(filename) {
|
||||||
msg.textContent = `Signing in and loading ${filename}...`;
|
msg.textContent = `Signing in and loading ${filename}...`;
|
||||||
|
if (!hasPuter()) {
|
||||||
|
msg.textContent = 'puter runtime not available.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
if (!puter.auth || !puter.auth.signIn) {
|
||||||
|
throw new Error('puter.auth.signIn not available');
|
||||||
|
}
|
||||||
const user = await puter.auth.signIn();
|
const user = await puter.auth.signIn();
|
||||||
if (!user) throw new Error('Sign in failed.');
|
if (!user) throw new Error('Sign in failed or cancelled.');
|
||||||
const path = `${DIR}/${filename}`;
|
// filename may sometimes already be a path
|
||||||
const data = await puter.fs.read(path);
|
const path = filename.startsWith(DIR + '/') ? filename : `${DIR}/${filename}`;
|
||||||
|
const data = await fsRead(path);
|
||||||
if (!data) throw new Error('File empty or unreadable.');
|
if (!data) throw new Error('File empty or unreadable.');
|
||||||
let cookies;
|
let cookies;
|
||||||
try {
|
if (typeof data === 'string') {
|
||||||
cookies = JSON.parse(data);
|
try {
|
||||||
} catch (parseErr) {
|
cookies = JSON.parse(data);
|
||||||
throw new Error('Saved data is not valid JSON.');
|
} catch (parseErr) {
|
||||||
|
// If content is not JSON but looks like key=value lines, attempt to parse
|
||||||
|
const maybe = {};
|
||||||
|
data.split(/\r?\n/).forEach(line => {
|
||||||
|
if (!line) return;
|
||||||
|
const [k, ...v] = line.split('=');
|
||||||
|
if (!k) return;
|
||||||
|
maybe[k.trim()] = decodeURIComponent((v || []).join('=').trim());
|
||||||
|
});
|
||||||
|
if (Object.keys(maybe).length > 0) cookies = maybe;
|
||||||
|
else throw new Error('Saved data is not valid JSON.');
|
||||||
|
}
|
||||||
|
} else if (typeof data === 'object') {
|
||||||
|
cookies = data;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported file data type');
|
||||||
}
|
}
|
||||||
setCookies(cookies);
|
setCookies(cookies);
|
||||||
msg.textContent = `Cookies restored from ${filename}. Reloading...`;
|
msg.textContent = `Cookies restored from ${filename}. Reloading...`;
|
||||||
setTimeout(() => location.reload(), 1200);
|
setTimeout(() => location.reload(), 900);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
msg.textContent = 'Error loading file: ' + (e.message || e);
|
msg.textContent = 'Error loading file: ' + (e.message || e);
|
||||||
|
console.error('loadFile error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteFile(filename) {
|
async function deleteFile(filename) {
|
||||||
if (!confirm(`Delete ${filename}? This cannot be undone.`)) return;
|
if (!confirm(`Delete ${filename}? This cannot be undone.`)) return;
|
||||||
msg.textContent = `Deleting ${filename}...`;
|
msg.textContent = `Deleting ${filename}...`;
|
||||||
|
if (!hasPuter()) {
|
||||||
|
msg.textContent = 'puter runtime not available.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
if (!puter.auth || !puter.auth.signIn) {
|
||||||
|
throw new Error('puter.auth.signIn not available');
|
||||||
|
}
|
||||||
const user = await puter.auth.signIn();
|
const user = await puter.auth.signIn();
|
||||||
if (!user) throw new Error('Sign in failed.');
|
if (!user) throw new Error('Sign in failed or cancelled.');
|
||||||
const path = `${DIR}/${filename}`;
|
const path = filename.startsWith(DIR + '/') ? filename : `${DIR}/${filename}`;
|
||||||
await puter.fs.delete(path);
|
await fsDelete(path);
|
||||||
msg.textContent = `Deleted ${filename}.`;
|
msg.textContent = `Deleted ${filename}.`;
|
||||||
await listFiles();
|
await listFiles();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
msg.textContent = 'Error deleting file: ' + (e.message || e);
|
msg.textContent = 'Error deleting file: ' + (e.message || e);
|
||||||
|
console.error('deleteFile error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearAllFiles() {
|
async function clearAllFiles() {
|
||||||
if (!confirm('Delete ALL cookie files in cloud?')) return;
|
if (!confirm('Delete ALL cookie files in cloud?')) return;
|
||||||
msg.textContent = 'Signing in and deleting all cookie files...';
|
msg.textContent = 'Signing in and deleting all cookie files...';
|
||||||
|
if (!hasPuter()) {
|
||||||
|
msg.textContent = 'puter runtime not available.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
if (!puter.auth || !puter.auth.signIn) {
|
||||||
|
throw new Error('puter.auth.signIn not available');
|
||||||
|
}
|
||||||
const user = await puter.auth.signIn();
|
const user = await puter.auth.signIn();
|
||||||
if (!user) throw new Error('Sign in failed.');
|
if (!user) throw new Error('Sign in failed or cancelled.');
|
||||||
const entries = await puter.fs.readdir(DIR).catch(() => null);
|
const entries = await fsReaddir(DIR).catch(() => null);
|
||||||
if (!entries || entries.length === 0) {
|
if (!entries || entries.length === 0) {
|
||||||
msg.textContent = 'No files to delete.';
|
msg.textContent = 'No files to delete.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const name of entries) {
|
for (const name of entries) {
|
||||||
await puter.fs.delete(`${DIR}/${name}`).catch(err => {
|
const path = name.startsWith(DIR + '/') ? name : `${DIR}/${name}`;
|
||||||
// continue deleting others even if one fails
|
await fsDelete(path).catch(err => {
|
||||||
console.warn('Failed deleting', name, err);
|
console.warn('Failed deleting', name, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -205,6 +365,7 @@
|
|||||||
await listFiles();
|
await listFiles();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
msg.textContent = 'Error clearing files: ' + (e.message || e);
|
msg.textContent = 'Error clearing files: ' + (e.message || e);
|
||||||
|
console.error('clearAllFiles error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,13 +375,21 @@
|
|||||||
document.getElementById('refresh').onclick = listFiles;
|
document.getElementById('refresh').onclick = listFiles;
|
||||||
document.getElementById('clearAll').onclick = clearAllFiles;
|
document.getElementById('clearAll').onclick = clearAllFiles;
|
||||||
|
|
||||||
// Initial load: ensure directory exists and list files (but don't require sign-in for listing)
|
// Initial load
|
||||||
(async () => {
|
(async () => {
|
||||||
|
if (!hasPuter()) {
|
||||||
|
msg.textContent = 'puter runtime not detected. Buttons that require cloud FS will be disabled.';
|
||||||
|
disableUI('puter not available');
|
||||||
|
filesListEl.textContent = 'puter runtime not detected.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// Try to list files; if dir missing, show no files
|
// Attempt to list files; if dir missing, listFiles handles it
|
||||||
|
enableUI();
|
||||||
await listFiles();
|
await listFiles();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Initial listing failed', e);
|
console.warn('Initial listing failed', e);
|
||||||
|
msg.textContent = 'Initial listing failed: ' + (e.message || e);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user