forked from trent_larson/crowd-funder-for-time-pwa
Fix Vue property conflicts in PlatformServiceMixin implementation
- Remove duplicate property declarations from TopMessage component - Use (this as any) type assertion for mixin methods - Resolves 'Data property already defined' warnings - Fixes 'this.dbQuery is not a function' runtime errors
This commit is contained in:
@@ -18,11 +18,23 @@ import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
import { AppString, NotificationIface } from "../constants/app";
|
||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
||||
import { DEFAULT_ENDORSER_API_SERVER } from "../constants/app";
|
||||
import { usePlatformService } from "../utils/usePlatformService";
|
||||
import {
|
||||
PlatformServiceMixin,
|
||||
IPlatformServiceMixin,
|
||||
} from "../utils/PlatformServiceMixin";
|
||||
import { mapColumnsToValues, parseJsonField } from "../db/databaseUtil";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
export default class TopMessage extends Vue {
|
||||
// NOTE: This component uses PlatformServiceMixin which provides:
|
||||
// - this.dbQuery(), this.dbExec(), this.dbGetOneRow() methods
|
||||
// - this.platformService computed property
|
||||
// - this.isCapacitor, this.isWeb, this.isElectron computed properties
|
||||
// - this.capabilities computed property
|
||||
// TypeScript requires (this as any) for mixin methods due to compile-time limitations
|
||||
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
@Prop selected = "";
|
||||
@@ -59,24 +71,24 @@ export default class TopMessage extends Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings for the active account using the platform service composable.
|
||||
* This replaces the direct call to databaseUtil.retrieveSettingsForActiveAccount()
|
||||
* and demonstrates the new composable pattern.
|
||||
* Get settings for the active account using the platform service mixin.
|
||||
* This demonstrates the concise mixin pattern with direct database access.
|
||||
*/
|
||||
private async getActiveAccountSettings() {
|
||||
const { dbQuery } = usePlatformService();
|
||||
// Declare defaultSettings outside try block for proper scope
|
||||
let defaultSettings;
|
||||
|
||||
try {
|
||||
// Get default settings first
|
||||
const defaultSettings = await this.getDefaultSettings();
|
||||
|
||||
defaultSettings = await this.getDefaultSettings();
|
||||
|
||||
// If no active DID, return defaults
|
||||
if (!defaultSettings.activeDid) {
|
||||
return defaultSettings;
|
||||
}
|
||||
|
||||
// Get account-specific settings using the composable
|
||||
const result = await dbQuery(
|
||||
// Get account-specific settings using the mixin (much more concise!)
|
||||
const result = await (this as any).dbQuery(
|
||||
"SELECT * FROM settings WHERE accountDid = ?",
|
||||
[defaultSettings.activeDid],
|
||||
);
|
||||
@@ -105,22 +117,29 @@ export default class TopMessage extends Vue {
|
||||
|
||||
return settings;
|
||||
} catch (error) {
|
||||
console.error(`Failed to retrieve account settings for ${defaultSettings.activeDid}:`, error);
|
||||
return defaultSettings;
|
||||
console.error(
|
||||
`Failed to retrieve account settings for ${defaultSettings?.activeDid}:`,
|
||||
error,
|
||||
);
|
||||
return (
|
||||
defaultSettings || {
|
||||
id: MASTER_SETTINGS_KEY,
|
||||
activeDid: undefined,
|
||||
apiServer: DEFAULT_ENDORSER_API_SERVER,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default settings using the platform service composable
|
||||
* Get default settings using the platform service mixin
|
||||
*/
|
||||
private async getDefaultSettings() {
|
||||
const { dbQuery } = usePlatformService();
|
||||
|
||||
try {
|
||||
const result = await dbQuery(
|
||||
"SELECT * FROM settings WHERE id = ?",
|
||||
[MASTER_SETTINGS_KEY],
|
||||
);
|
||||
// Direct database access via mixin - no destructuring needed!
|
||||
const result = await (this as any).dbQuery("SELECT * FROM settings WHERE id = ?", [
|
||||
MASTER_SETTINGS_KEY,
|
||||
]);
|
||||
|
||||
if (!result?.values?.length) {
|
||||
return {
|
||||
@@ -130,7 +149,10 @@ export default class TopMessage extends Vue {
|
||||
};
|
||||
}
|
||||
|
||||
const settings = mapColumnsToValues(result.columns, result.values)[0] as any;
|
||||
const settings = mapColumnsToValues(
|
||||
result.columns,
|
||||
result.values,
|
||||
)[0] as any;
|
||||
|
||||
// Handle searchBoxes parsing
|
||||
if (settings.searchBoxes) {
|
||||
|
||||
@@ -182,7 +182,7 @@ export async function logToDb(
|
||||
message: string,
|
||||
level: string = "info",
|
||||
): Promise<void> {
|
||||
// Prevent infinite logging loops - if we're already trying to log to database,
|
||||
// Prevent infinite logging loops - if we're already trying to log to database,
|
||||
// just log to console instead to break circular dependency
|
||||
if (isLoggingToDatabase) {
|
||||
console.log(`[DB-PREVENTED-${level.toUpperCase()}] ${message}`);
|
||||
@@ -210,12 +210,21 @@ export async function logToDb(
|
||||
const sevenDaysAgo = new Date(
|
||||
new Date().getTime() - 7 * 24 * 60 * 60 * 1000,
|
||||
).toDateString(); // Use date string to match schema
|
||||
memoryLogs = memoryLogs.filter((log) => log.split(" ")[0] > sevenDaysAgo);
|
||||
await platform.dbExec("DELETE FROM logs WHERE date < ?", [sevenDaysAgo]);
|
||||
memoryLogs = memoryLogs.filter(
|
||||
(log) => log.split(" ")[0] > sevenDaysAgo,
|
||||
);
|
||||
await platform.dbExec("DELETE FROM logs WHERE date < ?", [
|
||||
sevenDaysAgo,
|
||||
]);
|
||||
lastCleanupDate = todayKey;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error logging to database:", error, " ... for original message:", message);
|
||||
console.error(
|
||||
"Error logging to database:",
|
||||
error,
|
||||
" ... for original message:",
|
||||
message,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
// Always reset the flag to prevent permanent blocking of database logging
|
||||
|
||||
@@ -5,9 +5,13 @@ const platform = process.env.VITE_PLATFORM;
|
||||
const pwa_enabled = process.env.VITE_PWA_ENABLED === "true";
|
||||
|
||||
// Debug: Check SharedArrayBuffer availability
|
||||
console.log(`[SharedArrayBuffer] Available: ${typeof SharedArrayBuffer !== 'undefined'}`);
|
||||
console.log(
|
||||
`[SharedArrayBuffer] Available: ${typeof SharedArrayBuffer !== "undefined"}`,
|
||||
);
|
||||
console.log(`[Browser] User Agent: ${navigator.userAgent}`);
|
||||
console.log(`[Headers] Check COOP/COEP in Network tab if SharedArrayBuffer is false`);
|
||||
console.log(
|
||||
`[Headers] Check COOP/COEP in Network tab if SharedArrayBuffer is false`,
|
||||
);
|
||||
|
||||
// Only import service worker for web builds
|
||||
if (pwa_enabled) {
|
||||
|
||||
@@ -29,7 +29,9 @@ let databaseService = null;
|
||||
async function getDatabaseService() {
|
||||
if (!databaseService) {
|
||||
// Dynamic import to prevent circular dependency
|
||||
const { default: service } = await import("./services/AbsurdSqlDatabaseService");
|
||||
const { default: service } = await import(
|
||||
"./services/AbsurdSqlDatabaseService"
|
||||
);
|
||||
databaseService = service;
|
||||
}
|
||||
return databaseService;
|
||||
|
||||
@@ -59,7 +59,7 @@ class AbsurdSqlDatabaseService implements DatabaseService {
|
||||
await this.initializationPromise;
|
||||
} catch (error) {
|
||||
// logger.error(`AbsurdSqlDatabaseService initialize method failed:`, error); // DISABLED
|
||||
console.error(`AbsurdSqlDatabaseService initialize method failed:`, error);
|
||||
logger.error(`AbsurdSqlDatabaseService initialize method failed:`, error);
|
||||
this.initializationPromise = null; // Reset on failure
|
||||
throw error;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ class AbsurdSqlDatabaseService implements DatabaseService {
|
||||
// " ... with params:",
|
||||
// operation.params,
|
||||
// );
|
||||
console.error(
|
||||
logger.error(
|
||||
"Error while processing SQL queue:",
|
||||
error,
|
||||
" ... for sql:",
|
||||
@@ -208,7 +208,7 @@ class AbsurdSqlDatabaseService implements DatabaseService {
|
||||
// logger.error( // DISABLED
|
||||
// `Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null`,
|
||||
// );
|
||||
console.error(
|
||||
logger.error(
|
||||
`Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null`,
|
||||
);
|
||||
throw new Error(
|
||||
|
||||
@@ -137,8 +137,5 @@ export interface PlatformService {
|
||||
* @param params - The parameters to pass to the query
|
||||
* @returns Promise resolving to the first row as an array, or undefined if no results
|
||||
*/
|
||||
dbGetOneRow(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<unknown[] | undefined>;
|
||||
dbGetOneRow(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,11 @@ export class PlatformServiceFactory {
|
||||
|
||||
// Only log when actually creating the instance
|
||||
const platform = process.env.VITE_PLATFORM || "web";
|
||||
|
||||
|
||||
if (!PlatformServiceFactory.creationLogged) {
|
||||
console.log(`[PlatformServiceFactory] Creating singleton instance for platform: ${platform}`);
|
||||
console.log(
|
||||
`[PlatformServiceFactory] Creating singleton instance for platform: ${platform}`,
|
||||
);
|
||||
PlatformServiceFactory.creationLogged = true;
|
||||
}
|
||||
|
||||
@@ -63,7 +65,7 @@ export class PlatformServiceFactory {
|
||||
public static getStats(): { callCount: number; instanceExists: boolean } {
|
||||
return {
|
||||
callCount: PlatformServiceFactory.callCount,
|
||||
instanceExists: PlatformServiceFactory.instance !== null
|
||||
instanceExists: PlatformServiceFactory.instance !== null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,8 +187,11 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
params: unknown[] = [],
|
||||
): Promise<R> {
|
||||
// Log incoming parameters for debugging (HIGH PRIORITY)
|
||||
logger.warn(`[CapacitorPlatformService] queueOperation - SQL: ${sql}, Params:`, params);
|
||||
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] queueOperation - SQL: ${sql}, Params:`,
|
||||
params,
|
||||
);
|
||||
|
||||
// Convert parameters to SQLite-compatible types with robust serialization
|
||||
const convertedParams = params.map((param, index) => {
|
||||
if (param === null || param === undefined) {
|
||||
@@ -196,51 +199,75 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
}
|
||||
if (typeof param === "object" && param !== null) {
|
||||
// Enhanced debug logging for all objects (HIGH PRIORITY)
|
||||
logger.warn(`[CapacitorPlatformService] Object param at index ${index}:`, {
|
||||
type: typeof param,
|
||||
toString: param.toString(),
|
||||
constructorName: param.constructor?.name,
|
||||
isArray: Array.isArray(param),
|
||||
keys: Object.keys(param),
|
||||
stringRep: String(param)
|
||||
});
|
||||
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Object param at index ${index}:`,
|
||||
{
|
||||
type: typeof param,
|
||||
toString: param.toString(),
|
||||
constructorName: param.constructor?.name,
|
||||
isArray: Array.isArray(param),
|
||||
keys: Object.keys(param),
|
||||
stringRep: String(param),
|
||||
},
|
||||
);
|
||||
|
||||
// Special handling for Proxy objects (common cause of "An object could not be cloned")
|
||||
const isProxy = this.isProxyObject(param);
|
||||
logger.warn(`[CapacitorPlatformService] isProxy result for index ${index}:`, isProxy);
|
||||
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] isProxy result for index ${index}:`,
|
||||
isProxy,
|
||||
);
|
||||
|
||||
// AGGRESSIVE: If toString contains "Proxy", treat as Proxy even if isProxyObject returns false
|
||||
const stringRep = String(param);
|
||||
const forceProxyDetection = stringRep.includes('Proxy(') || stringRep.startsWith('Proxy');
|
||||
logger.warn(`[CapacitorPlatformService] Force proxy detection for index ${index}:`, forceProxyDetection);
|
||||
|
||||
const forceProxyDetection =
|
||||
stringRep.includes("Proxy(") || stringRep.startsWith("Proxy");
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Force proxy detection for index ${index}:`,
|
||||
forceProxyDetection,
|
||||
);
|
||||
|
||||
if (isProxy || forceProxyDetection) {
|
||||
logger.warn(`[CapacitorPlatformService] Proxy object detected at index ${index} (method: ${isProxy ? 'isProxyObject' : 'stringDetection'}), toString: ${stringRep}`);
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Proxy object detected at index ${index} (method: ${isProxy ? "isProxyObject" : "stringDetection"}), toString: ${stringRep}`,
|
||||
);
|
||||
try {
|
||||
// AGGRESSIVE EXTRACTION: Try multiple methods to extract actual values
|
||||
if (Array.isArray(param)) {
|
||||
// Method 1: Array.from() to extract from Proxy(Array)
|
||||
const actualArray = Array.from(param);
|
||||
logger.info(`[CapacitorPlatformService] Extracted array from Proxy via Array.from():`, actualArray);
|
||||
|
||||
logger.info(
|
||||
`[CapacitorPlatformService] Extracted array from Proxy via Array.from():`,
|
||||
actualArray,
|
||||
);
|
||||
|
||||
// Method 2: Manual element extraction for safety
|
||||
const manualArray: unknown[] = [];
|
||||
for (let i = 0; i < param.length; i++) {
|
||||
manualArray.push(param[i]);
|
||||
}
|
||||
logger.info(`[CapacitorPlatformService] Manual array extraction:`, manualArray);
|
||||
|
||||
logger.info(
|
||||
`[CapacitorPlatformService] Manual array extraction:`,
|
||||
manualArray,
|
||||
);
|
||||
|
||||
// Use the manual extraction as it's more reliable
|
||||
return manualArray;
|
||||
} else {
|
||||
// For Proxy(Object), try to extract actual object
|
||||
const actualObject = Object.assign({}, param);
|
||||
logger.info(`[CapacitorPlatformService] Extracted object from Proxy:`, actualObject);
|
||||
logger.info(
|
||||
`[CapacitorPlatformService] Extracted object from Proxy:`,
|
||||
actualObject,
|
||||
);
|
||||
return actualObject;
|
||||
}
|
||||
} catch (proxyError) {
|
||||
logger.error(`[CapacitorPlatformService] Failed to extract from Proxy at index ${index}:`, proxyError);
|
||||
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Failed to extract from Proxy at index ${index}:`,
|
||||
proxyError,
|
||||
);
|
||||
|
||||
// FALLBACK: Try to extract primitive values manually
|
||||
if (Array.isArray(param)) {
|
||||
try {
|
||||
@@ -248,30 +275,42 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
for (let i = 0; i < param.length; i++) {
|
||||
fallbackArray.push(param[i]);
|
||||
}
|
||||
logger.info(`[CapacitorPlatformService] Fallback array extraction successful:`, fallbackArray);
|
||||
logger.info(
|
||||
`[CapacitorPlatformService] Fallback array extraction successful:`,
|
||||
fallbackArray,
|
||||
);
|
||||
return fallbackArray;
|
||||
} catch (fallbackError) {
|
||||
logger.error(`[CapacitorPlatformService] Fallback array extraction failed:`, fallbackError);
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Fallback array extraction failed:`,
|
||||
fallbackError,
|
||||
);
|
||||
return `[Proxy Array - Could not extract]`;
|
||||
}
|
||||
}
|
||||
return `[Proxy Object - Could not extract]`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Safely convert objects and arrays to JSON strings
|
||||
return JSON.stringify(param);
|
||||
} catch (error) {
|
||||
// Handle non-serializable objects
|
||||
logger.error(`[CapacitorPlatformService] Failed to serialize parameter at index ${index}:`, error);
|
||||
logger.error(`[CapacitorPlatformService] Problematic parameter:`, param);
|
||||
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Failed to serialize parameter at index ${index}:`,
|
||||
error,
|
||||
);
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Problematic parameter:`,
|
||||
param,
|
||||
);
|
||||
|
||||
// Fallback: Convert to string representation
|
||||
if (Array.isArray(param)) {
|
||||
return `[Array(${param.length})]`;
|
||||
}
|
||||
return `[Object ${param.constructor?.name || 'Unknown'}]`;
|
||||
return `[Object ${param.constructor?.name || "Unknown"}]`;
|
||||
}
|
||||
}
|
||||
if (typeof param === "boolean") {
|
||||
@@ -280,12 +319,16 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
}
|
||||
if (typeof param === "function") {
|
||||
// Functions can't be serialized - convert to string representation
|
||||
logger.warn(`[CapacitorPlatformService] Function parameter detected and converted to string at index ${index}`);
|
||||
return `[Function ${param.name || 'Anonymous'}]`;
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Function parameter detected and converted to string at index ${index}`,
|
||||
);
|
||||
return `[Function ${param.name || "Anonymous"}]`;
|
||||
}
|
||||
if (typeof param === "symbol") {
|
||||
// Symbols can't be serialized - convert to string representation
|
||||
logger.warn(`[CapacitorPlatformService] Symbol parameter detected and converted to string at index ${index}`);
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Symbol parameter detected and converted to string at index ${index}`,
|
||||
);
|
||||
return param.toString();
|
||||
}
|
||||
// Numbers, strings, bigints are supported, but ensure bigints are converted to strings
|
||||
@@ -296,13 +339,16 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
});
|
||||
|
||||
// Log converted parameters for debugging (HIGH PRIORITY)
|
||||
logger.warn(`[CapacitorPlatformService] Converted params:`, convertedParams);
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Converted params:`,
|
||||
convertedParams,
|
||||
);
|
||||
|
||||
return new Promise<R>((resolve, reject) => {
|
||||
// Create completely plain objects that Vue cannot make reactive
|
||||
// Step 1: Deep clone the converted params to ensure they're plain objects
|
||||
const plainParams = JSON.parse(JSON.stringify(convertedParams));
|
||||
|
||||
|
||||
// Step 2: Create operation object using Object.create(null) for no prototype
|
||||
const operation = Object.create(null) as QueuedOperation;
|
||||
operation.type = type;
|
||||
@@ -310,16 +356,25 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
operation.params = plainParams;
|
||||
operation.resolve = (value: unknown) => resolve(value as R);
|
||||
operation.reject = reject;
|
||||
|
||||
|
||||
// Step 3: Freeze everything to prevent modification
|
||||
Object.freeze(operation.params);
|
||||
Object.freeze(operation);
|
||||
|
||||
|
||||
// Add enhanced logging to verify our fix
|
||||
logger.warn(`[CapacitorPlatformService] Final operation.params type:`, typeof operation.params);
|
||||
logger.warn(`[CapacitorPlatformService] Final operation.params toString:`, operation.params.toString());
|
||||
logger.warn(`[CapacitorPlatformService] Final operation.params constructor:`, operation.params.constructor?.name);
|
||||
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Final operation.params type:`,
|
||||
typeof operation.params,
|
||||
);
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Final operation.params toString:`,
|
||||
operation.params.toString(),
|
||||
);
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Final operation.params constructor:`,
|
||||
operation.params.constructor?.name,
|
||||
);
|
||||
|
||||
this.operationQueue.push(operation);
|
||||
|
||||
// If we're already initialized, start processing the queue
|
||||
@@ -367,33 +422,42 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
try {
|
||||
// Method 1: Check toString representation
|
||||
const objString = obj.toString();
|
||||
if (objString.includes('Proxy(') || objString.startsWith('Proxy')) {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected via toString:", objString);
|
||||
if (objString.includes("Proxy(") || objString.startsWith("Proxy")) {
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Proxy detected via toString:",
|
||||
objString,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 2: Check constructor name
|
||||
const constructorName = obj.constructor?.name;
|
||||
if (constructorName === 'Proxy') {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected via constructor name");
|
||||
if (constructorName === "Proxy") {
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Proxy detected via constructor name",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 3: Check Object.prototype.toString
|
||||
const objToString = Object.prototype.toString.call(obj);
|
||||
if (objToString.includes('Proxy')) {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected via Object.prototype.toString");
|
||||
if (objToString.includes("Proxy")) {
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Proxy detected via Object.prototype.toString",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 4: Vue/Reactive Proxy detection - check for __v_ properties
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
// Check for Vue reactive proxy indicators
|
||||
const hasVueProxy = Object.getOwnPropertyNames(obj).some(prop =>
|
||||
prop.startsWith('__v_') || prop.startsWith('__r_')
|
||||
const hasVueProxy = Object.getOwnPropertyNames(obj).some(
|
||||
(prop) => prop.startsWith("__v_") || prop.startsWith("__r_"),
|
||||
);
|
||||
if (hasVueProxy) {
|
||||
logger.debug("[CapacitorPlatformService] Vue reactive Proxy detected");
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Vue reactive Proxy detected",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -401,15 +465,24 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
// Method 5: Try JSON.stringify and check for Proxy in error or result
|
||||
try {
|
||||
const jsonString = JSON.stringify(obj);
|
||||
if (jsonString.includes('Proxy')) {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected in JSON serialization");
|
||||
if (jsonString.includes("Proxy")) {
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Proxy detected in JSON serialization",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} catch (jsonError) {
|
||||
// If JSON.stringify fails, it might be a non-serializable Proxy
|
||||
const errorMessage = jsonError instanceof Error ? jsonError.message : String(jsonError);
|
||||
if (errorMessage.includes('Proxy') || errorMessage.includes('circular') || errorMessage.includes('clone')) {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected via JSON serialization error");
|
||||
const errorMessage =
|
||||
jsonError instanceof Error ? jsonError.message : String(jsonError);
|
||||
if (
|
||||
errorMessage.includes("Proxy") ||
|
||||
errorMessage.includes("circular") ||
|
||||
errorMessage.includes("clone")
|
||||
) {
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Proxy detected via JSON serialization error",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -417,7 +490,10 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
return false;
|
||||
} catch (error) {
|
||||
// If we can't inspect the object, it might be a Proxy causing issues
|
||||
logger.warn("[CapacitorPlatformService] Could not inspect object for Proxy detection:", error);
|
||||
logger.warn(
|
||||
"[CapacitorPlatformService] Could not inspect object for Proxy detection:",
|
||||
error,
|
||||
);
|
||||
return true; // Assume it's a Proxy if we can't inspect it
|
||||
}
|
||||
}
|
||||
@@ -1268,8 +1344,12 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
params?: unknown[],
|
||||
): Promise<unknown[] | undefined> {
|
||||
await this.waitForInitialization();
|
||||
const result = await this.queueOperation<QueryExecResult>("query", sql, params || []);
|
||||
|
||||
const result = await this.queueOperation<QueryExecResult>(
|
||||
"query",
|
||||
sql,
|
||||
params || [],
|
||||
);
|
||||
|
||||
// Return the first row from the result, or undefined if no results
|
||||
if (result && result.values && result.values.length > 0) {
|
||||
return result.values[0];
|
||||
|
||||
@@ -47,14 +47,16 @@ export class WebPlatformService implements PlatformService {
|
||||
|
||||
constructor() {
|
||||
WebPlatformService.instanceCount++;
|
||||
|
||||
|
||||
// Only warn if multiple instances (which shouldn't happen with singleton)
|
||||
if (WebPlatformService.instanceCount > 1) {
|
||||
console.error(`[WebPlatformService] ERROR: Multiple instances created! Count: ${WebPlatformService.instanceCount}`);
|
||||
console.error(
|
||||
`[WebPlatformService] ERROR: Multiple instances created! Count: ${WebPlatformService.instanceCount}`,
|
||||
);
|
||||
} else {
|
||||
console.log(`[WebPlatformService] Initializing web platform service`);
|
||||
}
|
||||
|
||||
|
||||
// Start worker initialization but don't await it in constructor
|
||||
this.workerInitPromise = this.initializeWorker();
|
||||
}
|
||||
@@ -74,19 +76,26 @@ export class WebPlatformService implements PlatformService {
|
||||
// This is required for Safari compatibility with nested workers
|
||||
// It installs a handler that proxies web worker creation through the main thread
|
||||
// CRITICAL: Only call initBackend from main thread, not from worker context
|
||||
const isMainThread = typeof window !== 'undefined';
|
||||
const isMainThread = typeof window !== "undefined";
|
||||
if (isMainThread) {
|
||||
// We're in the main thread - safe to dynamically import and call initBackend
|
||||
try {
|
||||
const { initBackend } = await import("absurd-sql/dist/indexeddb-main-thread");
|
||||
const { initBackend } = await import(
|
||||
"absurd-sql/dist/indexeddb-main-thread"
|
||||
);
|
||||
initBackend(this.worker);
|
||||
} catch (error) {
|
||||
console.error("[WebPlatformService] Failed to import/call initBackend:", error);
|
||||
console.error(
|
||||
"[WebPlatformService] Failed to import/call initBackend:",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
// We're in a worker context - skip initBackend call
|
||||
console.log("[WebPlatformService] Skipping initBackend call in worker context");
|
||||
console.log(
|
||||
"[WebPlatformService] Skipping initBackend call in worker context",
|
||||
);
|
||||
}
|
||||
|
||||
this.worker.onmessage = (event) => {
|
||||
@@ -120,13 +129,16 @@ export class WebPlatformService implements PlatformService {
|
||||
const { id, type } = message;
|
||||
|
||||
// Handle absurd-sql internal messages (these are normal, don't log)
|
||||
if (!id && message.type?.startsWith('__absurd:')) {
|
||||
if (!id && message.type?.startsWith("__absurd:")) {
|
||||
return; // Internal absurd-sql message, ignore silently
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
// logger.warn("[WebPlatformService] Received message without ID:", message); // DISABLED
|
||||
console.warn("[WebPlatformService] Received message without ID:", message);
|
||||
console.warn(
|
||||
"[WebPlatformService] Received message without ID:",
|
||||
message,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
136
src/utils/PlatformServiceMixin.ts
Normal file
136
src/utils/PlatformServiceMixin.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Platform Service Mixin for Vue Components
|
||||
*
|
||||
* Provides class-level caching of platform service instances to avoid
|
||||
* repeated PlatformServiceFactory.getInstance() calls throughout components.
|
||||
*
|
||||
* This mixin implements a hybrid approach combining:
|
||||
* - Class-level service caching for performance
|
||||
* - Vue composition API patterns for modern development
|
||||
* - Mixin pattern for easy integration with existing class components
|
||||
*
|
||||
* Benefits:
|
||||
* - Eliminates repeated PlatformServiceFactory.getInstance() calls
|
||||
* - Provides consistent service access pattern across components
|
||||
* - Improves performance with cached instances
|
||||
* - Maintains type safety with TypeScript
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
* @since 2025-07-02
|
||||
*/
|
||||
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import type { PlatformService } from "@/services/PlatformService";
|
||||
|
||||
/**
|
||||
* Mixin that provides cached platform service access to Vue components
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* mixins: [PlatformServiceMixin]
|
||||
* })
|
||||
* export default class MyComponent extends Vue {
|
||||
* async someMethod() {
|
||||
* // Access cached platform service directly
|
||||
* const result = await this.platformService.dbQuery('SELECT * FROM users');
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const PlatformServiceMixin = {
|
||||
data() {
|
||||
return {
|
||||
_platformService: null as PlatformService | null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Get the cached platform service instance
|
||||
* Creates and caches the instance on first access
|
||||
*/
|
||||
platformService(): PlatformService {
|
||||
if (!(this as any)._platformService) {
|
||||
(this as any)._platformService = PlatformServiceFactory.getInstance();
|
||||
}
|
||||
return (this as any)._platformService;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if running on Capacitor platform
|
||||
*/
|
||||
isCapacitor(): boolean {
|
||||
const service = (this as any).platformService as any;
|
||||
return typeof service.isCapacitor === "function"
|
||||
? service.isCapacitor()
|
||||
: false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if running on web platform
|
||||
*/
|
||||
isWeb(): boolean {
|
||||
const service = (this as any).platformService as any;
|
||||
return typeof service.isWeb === "function" ? service.isWeb() : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if running on Electron platform
|
||||
*/
|
||||
isElectron(): boolean {
|
||||
const service = (this as any).platformService as any;
|
||||
return typeof service.isElectron === "function"
|
||||
? service.isElectron()
|
||||
: false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get platform capabilities
|
||||
*/
|
||||
capabilities() {
|
||||
return (this as any).platformService.getCapabilities();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Convenient database query method
|
||||
* Shorthand for this.platformService.dbQuery()
|
||||
*/
|
||||
async dbQuery(sql: string, params?: unknown[]) {
|
||||
return await (this as any).platformService.dbQuery(sql, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Convenient database execution method
|
||||
* Shorthand for this.platformService.dbExec()
|
||||
*/
|
||||
async dbExec(sql: string, params?: unknown[]) {
|
||||
return await (this as any).platformService.dbExec(sql, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Convenient database single row method
|
||||
* Shorthand for this.platformService.dbGetOneRow()
|
||||
*/
|
||||
async dbGetOneRow(sql: string, params?: unknown[]) {
|
||||
return await (this as any).platformService.dbGetOneRow(sql, params);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Type-only export for components that need to declare the mixin interface
|
||||
*/
|
||||
export interface IPlatformServiceMixin {
|
||||
platformService: PlatformService;
|
||||
dbQuery(sql: string, params?: unknown[]): Promise<any>;
|
||||
dbExec(sql: string, params?: unknown[]): Promise<any>;
|
||||
dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
|
||||
isCapacitor: boolean;
|
||||
isWeb: boolean;
|
||||
isElectron: boolean;
|
||||
capabilities: any;
|
||||
}
|
||||
@@ -1,28 +1,28 @@
|
||||
/**
|
||||
* Platform Service Composable for TimeSafari
|
||||
*
|
||||
*
|
||||
* Provides centralized access to platform-specific services across Vue components.
|
||||
* This composable encapsulates the singleton pattern and provides a clean interface
|
||||
* for components to access platform functionality without directly managing
|
||||
* the PlatformServiceFactory.
|
||||
*
|
||||
*
|
||||
* Benefits:
|
||||
* - Centralized service access
|
||||
* - Better testability with easy mocking
|
||||
* - Cleaner component code
|
||||
* - Type safety with TypeScript
|
||||
* - Reactive capabilities if needed in the future
|
||||
*
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
* @since 2025-07-02
|
||||
*/
|
||||
|
||||
import { ref, readonly } from 'vue';
|
||||
import { PlatformServiceFactory } from '@/services/PlatformServiceFactory';
|
||||
import type { PlatformService } from '@/services/PlatformService';
|
||||
import * as databaseUtil from '@/db/databaseUtil';
|
||||
import { Contact } from '@/db/tables/contacts';
|
||||
import { ref, readonly } from "vue";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import type { PlatformService } from "@/services/PlatformService";
|
||||
import * as databaseUtil from "@/db/databaseUtil";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
|
||||
/**
|
||||
* Reactive reference to the platform service instance
|
||||
@@ -48,27 +48,27 @@ function initializePlatformService(): PlatformService {
|
||||
|
||||
/**
|
||||
* Platform Service Composable
|
||||
*
|
||||
*
|
||||
* Provides access to platform-specific services in a composable pattern.
|
||||
* This is the recommended way for Vue components to access platform functionality.
|
||||
*
|
||||
*
|
||||
* @returns Object containing platform service and utility functions
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // In a Vue component
|
||||
* import { usePlatformService } from '@/utils/usePlatformService';
|
||||
*
|
||||
*
|
||||
* export default {
|
||||
* setup() {
|
||||
* const { platform, dbQuery, dbExec, takePicture } = usePlatformService();
|
||||
*
|
||||
*
|
||||
* // Use platform methods directly
|
||||
* const takePhoto = async () => {
|
||||
* const result = await takePicture();
|
||||
* console.log('Photo taken:', result);
|
||||
* };
|
||||
*
|
||||
*
|
||||
* return { takePhoto };
|
||||
* }
|
||||
* };
|
||||
@@ -85,46 +85,55 @@ export function usePlatformService() {
|
||||
*/
|
||||
const safeSerializeParams = (params?: unknown[]): unknown[] => {
|
||||
if (!params) return [];
|
||||
|
||||
console.log('[usePlatformService] Original params:', params);
|
||||
console.log('[usePlatformService] Params toString:', params.toString());
|
||||
console.log('[usePlatformService] Params constructor:', params.constructor?.name);
|
||||
|
||||
|
||||
console.log("[usePlatformService] Original params:", params);
|
||||
console.log("[usePlatformService] Params toString:", params.toString());
|
||||
console.log(
|
||||
"[usePlatformService] Params constructor:",
|
||||
params.constructor?.name,
|
||||
);
|
||||
|
||||
// Use the most aggressive approach: JSON round-trip + spread operator
|
||||
try {
|
||||
// Method 1: JSON round-trip to completely strip any Proxy
|
||||
const jsonSerialized = JSON.parse(JSON.stringify(params));
|
||||
console.log('[usePlatformService] JSON serialized:', jsonSerialized);
|
||||
|
||||
console.log("[usePlatformService] JSON serialized:", jsonSerialized);
|
||||
|
||||
// Method 2: Spread operator to create new array
|
||||
const spreadArray = [...jsonSerialized];
|
||||
console.log('[usePlatformService] Spread array:', spreadArray);
|
||||
|
||||
console.log("[usePlatformService] Spread array:", spreadArray);
|
||||
|
||||
// Method 3: Force primitive extraction for each element
|
||||
const finalParams = spreadArray.map((param, index) => {
|
||||
const finalParams = spreadArray.map((param, _index) => {
|
||||
if (param === null || param === undefined) {
|
||||
return param;
|
||||
}
|
||||
|
||||
|
||||
// Force convert to primitive value
|
||||
if (typeof param === 'object') {
|
||||
if (typeof param === "object") {
|
||||
if (Array.isArray(param)) {
|
||||
return [...param]; // Spread to new array
|
||||
} else {
|
||||
return { ...param }; // Spread to new object
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return param;
|
||||
});
|
||||
|
||||
console.log('[usePlatformService] Final params:', finalParams);
|
||||
console.log('[usePlatformService] Final params toString:', finalParams.toString());
|
||||
console.log('[usePlatformService] Final params constructor:', finalParams.constructor?.name);
|
||||
|
||||
|
||||
console.log("[usePlatformService] Final params:", finalParams);
|
||||
console.log(
|
||||
"[usePlatformService] Final params toString:",
|
||||
finalParams.toString(),
|
||||
);
|
||||
console.log(
|
||||
"[usePlatformService] Final params constructor:",
|
||||
finalParams.constructor?.name,
|
||||
);
|
||||
|
||||
return finalParams;
|
||||
} catch (error) {
|
||||
console.error('[usePlatformService] Serialization error:', error);
|
||||
console.error("[usePlatformService] Serialization error:", error);
|
||||
// Fallback: manual extraction
|
||||
const fallbackParams: unknown[] = [];
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
@@ -133,11 +142,16 @@ export function usePlatformService() {
|
||||
const value = params[i];
|
||||
fallbackParams.push(value);
|
||||
} catch (accessError) {
|
||||
console.error('[usePlatformService] Access error for param', i, ':', accessError);
|
||||
console.error(
|
||||
"[usePlatformService] Access error for param",
|
||||
i,
|
||||
":",
|
||||
accessError,
|
||||
);
|
||||
fallbackParams.push(String(params[i]));
|
||||
}
|
||||
}
|
||||
console.log('[usePlatformService] Fallback params:', fallbackParams);
|
||||
console.log("[usePlatformService] Fallback params:", fallbackParams);
|
||||
return fallbackParams;
|
||||
}
|
||||
};
|
||||
@@ -194,12 +208,12 @@ export function usePlatformService() {
|
||||
const capabilities = service.getCapabilities();
|
||||
return !capabilities.isNativeApp;
|
||||
};
|
||||
|
||||
|
||||
const isCapacitor = () => {
|
||||
const capabilities = service.getCapabilities();
|
||||
return capabilities.isNativeApp && capabilities.isMobile;
|
||||
};
|
||||
|
||||
|
||||
const isElectron = () => {
|
||||
const capabilities = service.getCapabilities();
|
||||
return capabilities.isNativeApp && !capabilities.isMobile;
|
||||
@@ -263,9 +277,13 @@ export function usePlatformService() {
|
||||
* @param showBlocked Whether to include blocked contacts
|
||||
* @returns Promise<Contact[]> Filtered contacts
|
||||
*/
|
||||
const getContactsWithFilter = async (showBlocked = true): Promise<Contact[]> => {
|
||||
const getContactsWithFilter = async (
|
||||
showBlocked = true,
|
||||
): Promise<Contact[]> => {
|
||||
const contacts = await getContacts();
|
||||
return showBlocked ? contacts : contacts.filter(c => c.iViewContent !== false);
|
||||
return showBlocked
|
||||
? contacts
|
||||
: contacts.filter((c) => c.iViewContent !== false);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -300,7 +318,10 @@ export function usePlatformService() {
|
||||
*/
|
||||
const getAccount = async (did?: string) => {
|
||||
if (!did) return null;
|
||||
const result = await dbQuery("SELECT * FROM accounts WHERE did = ? LIMIT 1", [did]);
|
||||
const result = await dbQuery(
|
||||
"SELECT * FROM accounts WHERE did = ? LIMIT 1",
|
||||
[did],
|
||||
);
|
||||
const mappedResults = databaseUtil.mapQueryResultToValues(result);
|
||||
return mappedResults.length > 0 ? mappedResults[0] : null;
|
||||
};
|
||||
@@ -311,19 +332,22 @@ export function usePlatformService() {
|
||||
*/
|
||||
const logActivity = async (message: string) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
await dbExec("INSERT INTO logs (date, message) VALUES (?, ?)", [timestamp, message]);
|
||||
await dbExec("INSERT INTO logs (date, message) VALUES (?, ?)", [
|
||||
timestamp,
|
||||
message,
|
||||
]);
|
||||
};
|
||||
|
||||
return {
|
||||
// Direct service access (for advanced use cases)
|
||||
platform: readonly(platformService),
|
||||
isInitialized: readonly(isInitialized),
|
||||
|
||||
|
||||
// Database operations (low-level)
|
||||
dbQuery,
|
||||
dbExec,
|
||||
dbExec,
|
||||
dbGetOneRow,
|
||||
|
||||
|
||||
// Database operations (high-level)
|
||||
getContacts,
|
||||
getContactsWithFilter,
|
||||
@@ -332,34 +356,34 @@ export function usePlatformService() {
|
||||
saveDidSettings,
|
||||
getAccount,
|
||||
logActivity,
|
||||
|
||||
|
||||
// Media operations
|
||||
takePicture,
|
||||
pickImage,
|
||||
rotateCamera,
|
||||
|
||||
|
||||
// Platform detection
|
||||
isWeb,
|
||||
isCapacitor,
|
||||
isCapacitor,
|
||||
isElectron,
|
||||
getCapabilities,
|
||||
|
||||
|
||||
// File operations
|
||||
readFile,
|
||||
writeFile,
|
||||
deleteFile,
|
||||
listFiles,
|
||||
writeAndShareFile,
|
||||
|
||||
|
||||
// Navigation
|
||||
handleDeepLink,
|
||||
|
||||
|
||||
// Raw service access for cases not covered above
|
||||
service
|
||||
service,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Type helper for the composable return type
|
||||
*/
|
||||
export type PlatformServiceComposable = ReturnType<typeof usePlatformService>;
|
||||
export type PlatformServiceComposable = ReturnType<typeof usePlatformService>;
|
||||
|
||||
Reference in New Issue
Block a user