thorough checks for FS Handle

This commit is contained in:
Seth Trowbridge 2025-08-24 09:12:37 -04:00
parent fe1f8cdc53
commit f38bd814ff
3 changed files with 80 additions and 63 deletions

38
app.js
View File

@ -1,5 +1,6 @@
import * as FSHandle from "./store-directory-handle.js"; import * as FSHandle from "./store-directory-handle.js";
/** @type {(type:string, attributes?:Record<string, string>, ...children:Array<string|HTMLElement>)=>HTMLElement} */
const H =(type, attributes={}, ...children)=> { const H =(type, attributes={}, ...children)=> {
const el =document.createElement(type); const el =document.createElement(type);
Object.entries(attributes).forEach(([name, data])=>{ Object.entries(attributes).forEach(([name, data])=>{
@ -23,22 +24,37 @@ const H =(type, attributes={}, ...children)=> {
const button = H("button"); const button = H("button");
const listRoom = H("ul"); const listRoom = H("ul");
/** @type {(handle:boolean)=>Promise<void>} */ /** @type {(errorAction?:()=>void)=>Promise<void>} */
async function ReloadAndRedraw(handle) async function ReloadAndRedraw(errorAction = ()=>{})
{ {
if(handle) listRoom.innerHTML = "";
try
{ {
const handle = await FSHandle.getDirectoryHandle();
if(!handle)
{
console.log("no fs handle")
throw new Error("no fs handle set yet")
}
button.setAttribute("disabled", true);
button.innerText = "loading...";
const module = await import("./data/room.js"+"?rand="+Math.random()); const module = await import("./data/room.js"+"?rand="+Math.random());
const rooms = module.default(); const rooms = module.default();
button.innerText = "change directory"; button.innerText = "change directory";
console.log("rooms loaded:", rooms); console.log("rooms loaded:", rooms);
listRoom.innerHTML = "";
Object.entries(rooms).forEach(([roomID, roomData])=> Object.entries(rooms).forEach(([roomID, roomData])=>
{ {
const listPass = H("ul"); const listPass = H("ul");
Object.entries(roomData.Pass).forEach(([passID, passData])=>{ Object.entries(roomData.Pass).forEach(([passID, passData])=>{
listPass.appendChild(H("li", {}, listPass.appendChild(H("li", {},
H("button", { onclick(){ passData.load();console.log(roomData);globalThis.ROOM=roomData; } }, passData.name) H("button", { onclick()
{
passData.load();
console.log(roomData);
globalThis.ROOM=roomData;
} }, passData.name)
)) ))
}); });
@ -50,24 +66,22 @@ async function ReloadAndRedraw(handle)
); );
}) })
} }
else catch(e)
{ {
errorAction();
button.innerText = "select directory"; button.innerText = "select directory";
} }
button.removeAttribute("disabled")
} }
let handle = false; await ReloadAndRedraw();
handle = await FSHandle.getDirectoryHandle();
await ReloadAndRedraw(handle);
button.addEventListener("click", async()=> button.addEventListener("click", async()=>
{ {
const directory = await globalThis.showDirectoryPicker(); const directory = await globalThis.showDirectoryPicker();
await FSHandle.setDirectoryHandle(directory); await FSHandle.setDirectoryHandle(directory);
await ReloadAndRedraw(()=>alert("Invalid directory"));
handle = await FSHandle.getDirectoryHandle();
await ReloadAndRedraw(handle);
}); });
document.body.appendChild(button); document.body.appendChild(button);

View File

@ -4,7 +4,6 @@ self.addEventListener('install', ()=> self.skipWaiting()); // Activate worker im
self.addEventListener('activate', ()=> self.clients.claim()); // Become available to all pages); self.addEventListener('activate', ()=> self.clients.claim()); // Become available to all pages);
self.addEventListener('fetch', (event) =>event.respondWith(Interceptor(event))); self.addEventListener('fetch', (event) =>event.respondWith(Interceptor(event)));
let handle = false;
async function Interceptor(event) async function Interceptor(event)
{ {
const url = new URL(event.request.url); const url = new URL(event.request.url);
@ -13,28 +12,14 @@ async function Interceptor(event)
if(parts[0] == "data" || parts[0] == "room") if(parts[0] == "data" || parts[0] == "room")
{ {
console.log("intercept", pathname) console.log("intercept:", pathname)
const extension = pathname.substring(pathname.lastIndexOf('.') + 1); const handle = await FSAccess.getDirectoryHandle();
// Intercept only JavaScript files
if ( extension == "js" || extension == "json" )
{
if(!handle)
{
handle = await FSAccess.getDirectoryHandle();
}
if(handle) if(handle)
{ {
try const file = await FSAccess.drilldown(handle, parts);
if(file)
{ {
let filePointer = handle;
for(let i=0; i<parts.length-1; i++)
{
filePointer = await filePointer.getDirectoryHandle(parts[i], {create: false});
}
filePointer = await filePointer.getFileHandle(parts[parts.length-1], {create: false});
const file = await filePointer.getFile();
const content = await file.text(); const content = await file.text();
return new Response(content, { return new Response(content, {
headers: { headers: {
'Content-Type': 'application/javascript', 'Content-Type': 'application/javascript',
@ -42,14 +27,10 @@ async function Interceptor(event)
} }
}); });
} }
catch(e)
{
console.log("couldnt find it", pathname)
return fetch(event.request);
} }
} console.log("couldnt find:", pathname);
} return new Response("404", {status:404});
} }
else else

View File

@ -1,4 +1,5 @@
// 📦 IndexedDB Helper // 📦 IndexedDB Helper
/** @type {()=>Promise<IDBDatabase>} */
function openDB() { function openDB() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request = indexedDB.open('directory-handle-db', 1); const request = indexedDB.open('directory-handle-db', 1);
@ -11,19 +12,21 @@ function openDB() {
} }
// 💾 Store a directory handle // 💾 Store a directory handle
/** @type {(handle:FileSystemDirectoryHandle)=>Promise<void>} */
export async function setDirectoryHandle(handle) { export async function setDirectoryHandle(handle) {
const db = await openDB(); const db = await openDB();
const tx = db.transaction('handles', 'readwrite'); const tx = db.transaction('handles', 'readwrite');
tx.objectStore('handles').put(handle, 'my-folder'); tx.objectStore('handles').put(handle, 'user-folder');
await tx.done; await tx.done;
} }
// 📂 Retrieve a directory handle // 📂 Retrieve a directory handle
/** @type {()=>Promise<FileSystemDirectoryHandle|false>} */
export async function getDirectoryHandle() { export async function getDirectoryHandle() {
const db = await openDB(); const db = await openDB();
const tx = db.transaction('handles', 'readonly'); const tx = db.transaction('handles', 'readonly');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const getRequest = tx.objectStore('handles').get('my-folder'); const getRequest = tx.objectStore('handles').get('user-folder');
getRequest.onsuccess = () => resolve(getRequest.result); getRequest.onsuccess = () => resolve(getRequest.result);
getRequest.onerror = () => { getRequest.onerror = () => {
console.error('Error retrieving directory handle:', getRequest.error); console.error('Error retrieving directory handle:', getRequest.error);
@ -32,21 +35,40 @@ export async function getDirectoryHandle() {
}); });
} }
// 🔐 Check or request permission /** @type {(handle:FileSystemDirectoryHandle, parts:string[])=>Promise<File|false>} */
async function verifyPermission(handle, mode = 'readwrite') { export async function drilldown(handle, parts)
const opts = { mode }; {
if ((await handle.queryPermission(opts)) === 'granted') return true; try
if ((await handle.requestPermission(opts)) === 'granted') return true; {
return false; let filePointer = handle;
for(let i=0; i<parts.length-1; i++)
{
filePointer = await filePointer.getDirectoryHandle(parts[i], {create: false});
}
const leaf = await filePointer.getFileHandle(parts[parts.length-1], {create: false});
return await leaf.getFile();
}
catch(e)
{
return false
}
} }
// // 🔐 Check or request permission
// 📌 Request persistent storage // async function verifyPermission(handle, mode = 'readwrite') {
async function ensurePersistentStorage() { // const opts = { mode };
if (navigator.storage && navigator.storage.persist) { // if ((await handle.queryPermission(opts)) === 'granted') return true;
const granted = await navigator.storage.persist(); // if ((await handle.requestPermission(opts)) === 'granted') return true;
console.log(granted // return false;
? '✅ Persistent storage granted.' // }
: '⚠️ Storage may be cleared under pressure.'); //
} //
} // // 📌 Request persistent storage
// async function ensurePersistentStorage() {
// if (navigator.storage && navigator.storage.persist) {
// const granted = await navigator.storage.persist();
// console.log(granted
// ? '✅ Persistent storage granted.'
// : '⚠️ Storage may be cleared under pressure.');
// }
// }