mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
The Now feed mixes sensor, eye, track, number, witness and room cards across Orbit, Sky, Ground and Inside, ordered so neighbours differ, with composited thumbnails and live public-camera frames. Every situation has a room: typed, place- and time-anchored posts, evidence badges, bridging-rated notes and a cited Known / Disputed / Unknown local synthesis, with cross-tab presence over BroadcastChannel. Example posts are labelled as such. Capture adds Live (10 s segments sealed into a hash chain, shown on the globe as a view frustum) and Upload (EXIF placement, Unverified tier), and an attach sheet that files any capture into a nearby situation. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VVYRAqiQj9Y1V9ZfG1RcDH
122 lines
3.6 KiB
JavaScript
122 lines
3.6 KiB
JavaScript
/**
|
|
* Minimal promise wrapper over the device's IndexedDB: signing keys, sealed
|
|
* captures (+ media), and round two's room posts, uploads and small state.
|
|
*/
|
|
|
|
const DB_NAME = 'gev-mobile';
|
|
const DB_VERSION = 2;
|
|
let dbp = null;
|
|
|
|
export function openDb() {
|
|
if (dbp) return dbp;
|
|
dbp = new Promise((resolve, reject) => {
|
|
if (!('indexedDB' in globalThis)) {
|
|
reject(new Error('IndexedDB is unavailable'));
|
|
return;
|
|
}
|
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
req.onupgradeneeded = () => {
|
|
const db = req.result;
|
|
if (!db.objectStoreNames.contains('keys'))
|
|
db.createObjectStore('keys', { keyPath: 'id' });
|
|
if (!db.objectStoreNames.contains('captures')) {
|
|
const s = db.createObjectStore('captures', { keyPath: 'id' });
|
|
s.createIndex('seq', 'seq');
|
|
}
|
|
if (!db.objectStoreNames.contains('media'))
|
|
db.createObjectStore('media', { keyPath: 'id' });
|
|
// v2: rooms and uploads.
|
|
if (!db.objectStoreNames.contains('posts')) {
|
|
const p = db.createObjectStore('posts', { keyPath: 'id' });
|
|
p.createIndex('room', 'room');
|
|
}
|
|
if (!db.objectStoreNames.contains('uploads'))
|
|
db.createObjectStore('uploads', { keyPath: 'id' });
|
|
if (!db.objectStoreNames.contains('kv'))
|
|
db.createObjectStore('kv', { keyPath: 'id' });
|
|
};
|
|
req.onsuccess = () => {
|
|
const db = req.result;
|
|
// Another tab upgraded the schema: let it, and reopen next time.
|
|
db.onversionchange = () => {
|
|
db.close();
|
|
dbp = null;
|
|
};
|
|
resolve(db);
|
|
};
|
|
req.onerror = () => reject(req.error);
|
|
req.onblocked = () => reject(new Error('IndexedDB upgrade blocked'));
|
|
});
|
|
dbp.catch(() => (dbp = null));
|
|
return dbp;
|
|
}
|
|
|
|
function wrap(req) {
|
|
return new Promise((resolve, reject) => {
|
|
req.onsuccess = () => resolve(req.result);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
export async function get(store, key) {
|
|
const db = await openDb();
|
|
return wrap(db.transaction(store).objectStore(store).get(key));
|
|
}
|
|
|
|
export async function put(store, value) {
|
|
const db = await openDb();
|
|
return wrap(db.transaction(store, 'readwrite').objectStore(store).put(value));
|
|
}
|
|
|
|
export async function del(store, key) {
|
|
const db = await openDb();
|
|
return wrap(
|
|
db.transaction(store, 'readwrite').objectStore(store).delete(key),
|
|
);
|
|
}
|
|
|
|
export async function all(store) {
|
|
const db = await openDb();
|
|
return wrap(db.transaction(store).objectStore(store).getAll());
|
|
}
|
|
|
|
/** Write a capture bundle and its media atomically. */
|
|
export async function putCapture(bundle, mediaBlob) {
|
|
const db = await openDb();
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(['captures', 'media'], 'readwrite');
|
|
tx.objectStore('captures').put(bundle);
|
|
tx.objectStore('media').put({ id: bundle.id, blob: mediaBlob });
|
|
tx.oncomplete = () => resolve(bundle);
|
|
tx.onerror = () => reject(tx.error);
|
|
tx.onabort = () => reject(tx.error);
|
|
});
|
|
}
|
|
|
|
export async function lastCapture() {
|
|
const db = await openDb();
|
|
return new Promise((resolve, reject) => {
|
|
const req = db
|
|
.transaction('captures')
|
|
.objectStore('captures')
|
|
.index('seq')
|
|
.openCursor(null, 'prev');
|
|
req.onsuccess = () => resolve(req.result?.value || null);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
export async function captureBySeq(seq) {
|
|
const db = await openDb();
|
|
return wrap(
|
|
db.transaction('captures').objectStore('captures').index('seq').get(seq),
|
|
);
|
|
}
|
|
|
|
export async function byIndex(store, index, key) {
|
|
const db = await openDb();
|
|
return wrap(
|
|
db.transaction(store).objectStore(store).index(index).getAll(key),
|
|
);
|
|
}
|