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:
Matthew Raymer
2025-07-02 09:58:07 +00:00
parent 7b1f891c63
commit b37f1d1d84
11 changed files with 446 additions and 158 deletions

View 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;
}

View File

@@ -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>;