From 3aada9949d51024a92a8b5c6cb70d12f9c3cac16 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 21 Dec 2025 19:59:48 +0000
Subject: [PATCH] =auth refactored via rest, referral system set up for Jane, some javascript consolidation
---
assets/js/concise/DataStore.js | 305 +++++++++++++++++---------------------------------
1 files changed, 105 insertions(+), 200 deletions(-)
diff --git a/assets/js/concise/DataStore.js b/assets/js/concise/DataStore.js
index ffe1891..3d46224 100644
--- a/assets/js/concise/DataStore.js
+++ b/assets/js/concise/DataStore.js
@@ -110,7 +110,7 @@
};
store.config.headers = {
- 'X-WP-Nonce': jvbSettings?.nonce,
+ 'X-WP-Nonce': window.auth.getNonce(),
...store.config.headers
};
@@ -183,49 +183,6 @@
}
/**
- * Normalize data before saving - convert Sets/Maps automatically
- */
- normalizeForStorage(obj) {
- if (obj === null || obj === undefined) return obj;
-
- // Convert Set to Array
- if (obj instanceof Set) {
- return Array.from(obj);
- }
-
- // Convert Map to Object
- if (obj instanceof Map) {
- return Object.fromEntries(obj);
- }
-
- // Preserve ArrayBuffer and TypedArrays (needed for blob storage)
- if (obj instanceof ArrayBuffer || ArrayBuffer.isView(obj)) {
- return obj;
- }
-
- // Preserve Date objects
- if (obj instanceof Date) {
- return obj;
- }
-
- // Handle Arrays
- if (Array.isArray(obj)) {
- return obj.map(item => this.normalizeForStorage(item));
- }
-
- // Handle Objects
- if (typeof obj === 'object') {
- const normalized = {};
- for (const [key, value] of Object.entries(obj)) {
- normalized[key] = this.normalizeForStorage(value);
- }
- return normalized;
- }
-
- return obj;
- }
-
- /**
* Convert FormData to plain object for storage
*/
formDataToObject(formData) {
@@ -286,63 +243,6 @@
}
/**
- * Strip DOM references from object
- */
- stripDOMReferences(obj, visited = new WeakSet()) {
- if (obj === null || obj === undefined) return obj;
-
- const type = typeof obj;
- if (type === 'string' || type === 'number' || type === 'boolean') {
- return obj;
- }
-
- // Prevent circular references
- if (type === 'object' && visited.has(obj)) {
- return '[Circular]';
- }
-
- // Remove DOM elements
- if (obj instanceof HTMLElement ||
- obj instanceof NodeList ||
- obj instanceof HTMLCollection ||
- obj.nodeType !== undefined) {
- return null;
- }
-
- // ✅ PRESERVE ArrayBuffer and TypedArrays (needed for blob storage)
- if (obj instanceof ArrayBuffer ||
- ArrayBuffer.isView(obj)) {
- return obj;
- }
-
- // Handle Date
- if (obj instanceof Date) {
- return obj;
- }
-
- // Handle Arrays
- if (Array.isArray(obj)) {
- visited.add(obj);
- return obj.map(item => this.stripDOMReferences(item, visited)).filter(v => v !== null);
- }
-
- // Handle Objects
- if (type === 'object') {
- visited.add(obj);
- const cleaned = {};
- for (const [key, value] of Object.entries(obj)) {
- const cleanedValue = this.stripDOMReferences(value, visited);
- if (cleanedValue !== null) {
- cleaned[key] = cleanedValue;
- }
- }
- return cleaned;
- }
-
- return obj;
- }
-
- /**
* Initialize database for a specific store
*/
async initDB(name) {
@@ -644,15 +544,37 @@
signal: controller.signal
});
- if (response.status === 304 && cached) {
+ if (response.status === 304) {
+ // 304 means "Not Modified" - use cached data if available
+ if (cached) {
+ this.notify(name, 'data-loaded', {
+ cached: true,
+ notModified: true,
+ items: cached.items || []
+ });
+ return cached;
+ }
+
+ // No cached data but server says not modified - return empty result
+ // This can happen on first load when cache headers exist but data doesn't
this.notify(name, 'data-loaded', {
- cached: true,
+ cached: false,
notModified: true,
- items: cached.items || []
+ items: []
});
- return cached;
+
+ // Initialize empty lastResponse
+ store.lastResponse = {
+ has_more: false,
+ total: 0,
+ pages: 1,
+ queue_stats: {}
+ };
+
+ return { items: [] };
}
+ // Now check for other non-OK responses
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@@ -662,7 +584,6 @@
if (store.config.useHttpCaching) {
this.storeResponseHeaders(name, cacheKey, response);
}
-
await this.processFetchedData(name, data, cacheKey);
this.notify(name, 'data-loaded', {
@@ -711,8 +632,30 @@
const store = this.stores.get(name);
const items = data.items || [];
- for (const item of items) {
- await this.save(name, item);
+ // Batch process all items in a single transaction
+ if (store.db && items.length > 0) {
+ const tx = store.db.transaction([store.config.storeName], 'readwrite');
+ const objectStore = tx.objectStore(store.config.storeName);
+
+ for (const item of items) {
+ const result = this.processForStorage(item, store.config.validateData);
+ if (result.valid) {
+ const key = this.getItemKey(result.data, store.config.keyPath);
+
+ // Store in memory
+ store.data.set(key, item);
+
+ // Queue for batch write
+ await objectStore.put(result.data);
+ }
+ }
+
+ // Wait for transaction to complete
+ await new Promise((resolve, reject) => {
+ tx.oncomplete = () => resolve();
+ tx.onerror = () => reject(tx.error);
+ });
+
}
const cacheEntry = {
@@ -727,9 +670,11 @@
await this.saveToCache(name, cacheKey, cacheEntry);
store.lastResponse = {
+ ...data,
has_more: data.has_more || false,
total: data.total || items.length,
- pages: data.pages || 1
+ pages: data.pages || 1,
+ queue_stats: data.queue_stats || {}
};
}
@@ -740,26 +685,11 @@
async save(name, item) {
const store = this.stores.get(name);
- // Auto-normalize Sets/Maps
- let processed = this.normalizeForStorage(item);
-
- if (processed.data instanceof FormData) {
- processed = {
- ...processed,
- data: this.formDataToObject(processed.data)
- };
+ const result = this.processForStorage(item, store.config.validateData);
+ if (!result.valid) {
+ throw new Error(`Non-serializable data: ${result.error}`);
}
-
- processed = this.stripDOMReferences(processed);
-
- // Validate data is serializable
- if (store.config.validateData) {
- const validation = this.validateSerializable(processed);
- if (!validation.valid) {
- console.error(`Cannot save non-serializable data to store "${name}":`, validation.error);
- throw new Error(`Non-serializable data: ${validation.error}`);
- }
- }
+ const processed = result.data;
const key = this.getItemKey(processed, store.config.keyPath);
@@ -777,102 +707,74 @@
return key;
}
- /**
- * Validate that data is IndexedDB-serializable
- * Rejects: DOM elements, FormData, Blobs, Functions, etc.
- */
- validateSerializable(obj, path = 'root') {
- // Primitives are fine
- if (obj === null || obj === undefined) {
- return { valid: true };
- }
+ processForStorage(obj, validate = true, path = 'root') {
+ if (obj === null || obj === undefined) return { valid: true, data: obj };
const type = typeof obj;
- if (type === 'string' || type === 'number' || type === 'boolean') {
- return { valid: true };
+
+ // Handle primitives
+ if (['string', 'number', 'boolean'].includes(type)) {
+ return { valid: true, data: obj };
}
- // Functions cannot be serialized
+ // Reject functions
if (type === 'function') {
- return {
- valid: false,
- error: `Function at ${path}`
- };
+ return validate ? { valid: false, error: `Function at ${path}` } : { valid: true, data: null };
}
- // Date is serializable
- if (obj instanceof Date) {
- return { valid: true };
+ // DOM elements
+ if (obj instanceof HTMLElement || obj.nodeType !== undefined) {
+ return validate ? { valid: false, error: `DOM element at ${path}` } : { valid: true, data: null };
}
- if (obj instanceof ArrayBuffer || ArrayBuffer.isView(obj)) {
- return { valid: true };
- }
-
- // Reject DOM elements
- if (obj instanceof HTMLElement ||
- obj instanceof NodeList ||
- obj instanceof HTMLCollection ||
- (obj.nodeType !== undefined)) {
- return {
- valid: false,
- error: `DOM element at ${path}`
- };
- }
-
- // Reject FormData
+ // FormData - convert and continue
if (obj instanceof FormData) {
- return {
- valid: false,
- error: `FormData at ${path}. Convert to object first.`
- };
+ return validate
+ ? { valid: false, error: `FormData at ${path}` }
+ : { valid: true, data: this.formDataToObject(obj) };
}
- // Reject Blobs/Files
- if (obj instanceof Blob || obj instanceof File) {
- return {
- valid: false,
- error: `Blob/File at ${path}. Handle file uploads separately.`
- };
+ // Preserve safe types
+ if (obj instanceof Date || obj instanceof ArrayBuffer || ArrayBuffer.isView(obj)) {
+ return { valid: true, data: obj };
+ }
+
+ // Convert Sets to Arrays
+ if (obj instanceof Set) {
+ const arr = Array.from(obj);
+ return this.processForStorage(arr, validate, path);
+ }
+
+ // Convert Maps to Objects
+ if (obj instanceof Map) {
+ obj = Object.fromEntries(obj);
}
// Arrays
if (Array.isArray(obj)) {
+ const processed = [];
for (let i = 0; i < obj.length; i++) {
- const result = this.validateSerializable(obj[i], `${path}[${i}]`);
+ const result = this.processForStorage(obj[i], validate, `${path}[${i}]`);
if (!result.valid) return result;
+ if (result.data !== null) processed.push(result.data);
}
- return { valid: true };
+ return { valid: true, data: processed };
}
- // Plain objects
+ // Objects
if (type === 'object') {
- // Check for Sets/Maps (IndexedDB doesn't support them)
- if (obj instanceof Set) {
- return {
- valid: false,
- error: `Set at ${path}. Convert to Array first: Array.from(set)`
- };
- }
- if (obj instanceof Map) {
- return {
- valid: false,
- error: `Map at ${path}. Convert to Object first: Object.fromEntries(map)`
- };
- }
-
- // Check all properties
+ const processed = {};
for (const [key, value] of Object.entries(obj)) {
- const result = this.validateSerializable(value, `${path}.${key}`);
+ const result = this.processForStorage(value, validate, `${path}.${key}`);
if (!result.valid) return result;
+ if (result.data !== null) processed[key] = result.data;
}
- return { valid: true };
+ return { valid: true, data: processed };
}
- return {
- valid: false,
- error: `Unknown type at ${path}: ${type}`
- };
+ return validate
+ ? { valid: false, error: `Unknown type at ${path}` }
+ : { valid: true, data: null };
}
async delete(name, id) {
@@ -1094,7 +996,6 @@
acc[key] = filters[key];
return acc;
}, {});
-
return JSON.stringify(normalized);
}
@@ -1144,6 +1045,10 @@
}
// Initialize singleton on DOMContentLoaded
-document.addEventListener('DOMContentLoaded', function() {
- window.jvbStore = new DataStore();
+document.addEventListener('DOMContentLoaded', async function() {
+ window.auth.subscribe((event) => {
+ if (event === 'auth-loaded') {
+ window.jvbStore = new DataStore();
+ }
+ });
});
--
Gitblit v1.10.0