refactor(test-app): consolidate native fetcher config and fix ES module issues
- Move native fetcher configuration from HomeView.vue to App.vue mounted() hook - Single source of truth for configuration on app startup - Removed duplicate configuration logic from HomeView - Added diagnostic logging to trace configuration flow - Fix ES module compatibility issue with Capacitor CLI - Replace direct logger import with lazy async loading in test-user-zero.ts - Prevents 'exports is not defined' error when Capacitor CLI loads config - Update refreshToken() and setBaseUrl() methods to async for logger access - Add centralized logger utility (src/lib/logger.ts) - Single ESLint whitelist location for console usage - Structured logging with levels and emoji support - Updated router/index.ts and stores/app.ts to use logger - Enhance Android notification deduplication - Add within-batch duplicate detection in fetch workers - Improve storage deduplication with alarm cancellation - Cancel alarms for removed duplicate notifications - Update UserZeroView.vue to await async refreshToken() call Fixes: - npx cap sync android ES module error - Duplicate notification accumulation - Console statement lint warnings All changes maintain backward compatibility and improve debugging visibility.
This commit is contained in:
@@ -15,5 +15,13 @@
|
|||||||
"@typescript-eslint/no-explicit-any": "warn",
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||||
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
||||||
}
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["test-apps/daily-notification-test/src/lib/logger.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -262,32 +262,55 @@ public class DailyNotificationFetchWorker extends Worker {
|
|||||||
java.util.List<NotificationContent> existingNotifications = storage.getAllNotifications();
|
java.util.List<NotificationContent> existingNotifications = storage.getAllNotifications();
|
||||||
long toleranceMs = 60 * 1000; // 1 minute tolerance for DST shifts
|
long toleranceMs = 60 * 1000; // 1 minute tolerance for DST shifts
|
||||||
|
|
||||||
|
// Track scheduled times in current batch to prevent within-batch duplicates
|
||||||
|
java.util.Set<Long> batchScheduledTimes = new java.util.HashSet<>();
|
||||||
|
|
||||||
// Save all contents and schedule notifications (with duplicate checking)
|
// Save all contents and schedule notifications (with duplicate checking)
|
||||||
int scheduledCount = 0;
|
int scheduledCount = 0;
|
||||||
int skippedCount = 0;
|
int skippedCount = 0;
|
||||||
for (NotificationContent content : contents) {
|
for (NotificationContent content : contents) {
|
||||||
try {
|
try {
|
||||||
// Check for duplicate notification at the same scheduled time
|
// Check for duplicate notification at the same scheduled time
|
||||||
// This ensures prefetch doesn't create a duplicate if a manual notification already exists
|
// First check within current batch (prevents duplicates in same fetch)
|
||||||
boolean duplicateFound = false;
|
long scheduledTime = content.getScheduledTime();
|
||||||
for (NotificationContent existing : existingNotifications) {
|
boolean duplicateInBatch = false;
|
||||||
if (Math.abs(existing.getScheduledTime() - content.getScheduledTime()) <= toleranceMs) {
|
for (Long batchTime : batchScheduledTimes) {
|
||||||
Log.w(TAG, "PR2: DUPLICATE_SKIP id=" + content.getId() +
|
if (Math.abs(batchTime - scheduledTime) <= toleranceMs) {
|
||||||
" existing_id=" + existing.getId() +
|
Log.w(TAG, "PR2: DUPLICATE_SKIP_BATCH id=" + content.getId() +
|
||||||
" scheduled_time=" + content.getScheduledTime() +
|
" scheduled_time=" + scheduledTime +
|
||||||
" time_diff_ms=" + Math.abs(existing.getScheduledTime() - content.getScheduledTime()));
|
" time_diff_ms=" + Math.abs(batchTime - scheduledTime));
|
||||||
duplicateFound = true;
|
duplicateInBatch = true;
|
||||||
skippedCount++;
|
skippedCount++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duplicateFound) {
|
if (duplicateInBatch) {
|
||||||
// Skip this notification - one already exists for this time
|
|
||||||
// Ensures one prefetch → one notification pairing
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Then check against existing notifications in storage
|
||||||
|
boolean duplicateInStorage = false;
|
||||||
|
for (NotificationContent existing : existingNotifications) {
|
||||||
|
if (Math.abs(existing.getScheduledTime() - scheduledTime) <= toleranceMs) {
|
||||||
|
Log.w(TAG, "PR2: DUPLICATE_SKIP_STORAGE id=" + content.getId() +
|
||||||
|
" existing_id=" + existing.getId() +
|
||||||
|
" scheduled_time=" + scheduledTime +
|
||||||
|
" time_diff_ms=" + Math.abs(existing.getScheduledTime() - scheduledTime));
|
||||||
|
duplicateInStorage = true;
|
||||||
|
skippedCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateInStorage) {
|
||||||
|
// Skip this notification - one already exists for this time
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark this scheduledTime as processed in current batch
|
||||||
|
batchScheduledTimes.add(scheduledTime);
|
||||||
|
|
||||||
// Save content to storage
|
// Save content to storage
|
||||||
storage.saveNotificationContent(content);
|
storage.saveNotificationContent(content);
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ public class DailyNotificationStorage {
|
|||||||
|
|
||||||
loadNotificationsFromStorage();
|
loadNotificationsFromStorage();
|
||||||
cleanupOldNotifications();
|
cleanupOldNotifications();
|
||||||
|
// Remove duplicates on startup and cancel their alarms/workers
|
||||||
|
java.util.List<String> removedIds = deduplicateNotifications();
|
||||||
|
cancelRemovedNotifications(removedIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -484,6 +487,99 @@ public class DailyNotificationStorage {
|
|||||||
getLastFetchTime());
|
getLastFetchTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove duplicate notifications (same scheduledTime within tolerance)
|
||||||
|
*
|
||||||
|
* Keeps the most recently created notification for each scheduledTime,
|
||||||
|
* removes older duplicates to prevent accumulation.
|
||||||
|
*
|
||||||
|
* @return List of notification IDs that were removed (for cancellation of alarms/workers)
|
||||||
|
*/
|
||||||
|
public java.util.List<String> deduplicateNotifications() {
|
||||||
|
try {
|
||||||
|
long toleranceMs = 60 * 1000; // 1 minute tolerance
|
||||||
|
java.util.Map<Long, NotificationContent> scheduledTimeMap = new java.util.HashMap<>();
|
||||||
|
java.util.List<String> idsToRemove = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
synchronized (notificationList) {
|
||||||
|
// First pass: find all duplicates, keep the one with latest fetchedAt
|
||||||
|
for (NotificationContent notification : notificationList) {
|
||||||
|
long scheduledTime = notification.getScheduledTime();
|
||||||
|
boolean foundMatch = false;
|
||||||
|
|
||||||
|
for (java.util.Map.Entry<Long, NotificationContent> entry : scheduledTimeMap.entrySet()) {
|
||||||
|
if (Math.abs(entry.getKey() - scheduledTime) <= toleranceMs) {
|
||||||
|
// Found a duplicate - keep the one with latest fetchedAt
|
||||||
|
if (notification.getFetchedAt() > entry.getValue().getFetchedAt()) {
|
||||||
|
idsToRemove.add(entry.getValue().getId());
|
||||||
|
entry.setValue(notification);
|
||||||
|
} else {
|
||||||
|
idsToRemove.add(notification.getId());
|
||||||
|
}
|
||||||
|
foundMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundMatch) {
|
||||||
|
scheduledTimeMap.put(scheduledTime, notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
if (!idsToRemove.isEmpty()) {
|
||||||
|
notificationList.removeIf(n -> idsToRemove.contains(n.getId()));
|
||||||
|
for (String id : idsToRemove) {
|
||||||
|
notificationCache.remove(id);
|
||||||
|
}
|
||||||
|
saveNotificationsToStorage();
|
||||||
|
Log.i(TAG, "DN|DEDUPE_CLEANUP removed=" + idsToRemove.size() + " duplicates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idsToRemove;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error during deduplication", e);
|
||||||
|
return new java.util.ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel alarms and workers for removed notification IDs
|
||||||
|
*
|
||||||
|
* This ensures that when notifications are removed (e.g., during deduplication),
|
||||||
|
* their associated alarms and WorkManager workers are also cancelled to prevent
|
||||||
|
* zombie workers trying to display non-existent notifications.
|
||||||
|
*
|
||||||
|
* @param removedIds List of notification IDs that were removed
|
||||||
|
*/
|
||||||
|
private void cancelRemovedNotifications(java.util.List<String> removedIds) {
|
||||||
|
if (removedIds == null || removedIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cancel alarms for removed notifications
|
||||||
|
DailyNotificationScheduler scheduler = new DailyNotificationScheduler(
|
||||||
|
context,
|
||||||
|
(android.app.AlarmManager) context.getSystemService(Context.ALARM_SERVICE)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (String id : removedIds) {
|
||||||
|
scheduler.cancelNotification(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: WorkManager workers can't be cancelled by notification ID directly
|
||||||
|
// Workers will handle missing content gracefully by returning Result.success()
|
||||||
|
// (see DailyNotificationWorker.handleDisplayNotification - it returns success for missing content)
|
||||||
|
// This prevents retry loops for notifications removed during deduplication
|
||||||
|
|
||||||
|
Log.i(TAG, "DN|DEDUPE_CLEANUP cancelled alarms for " + removedIds.size() + " removed notifications");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "DN|DEDUPE_CLEANUP_ERR failed to cancel alarms/workers", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enforce storage limits and retention policy
|
* Enforce storage limits and retention policy
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -131,8 +131,10 @@ public class DailyNotificationWorker extends Worker {
|
|||||||
NotificationContent content = getContentFromRoomOrLegacy(notificationId);
|
NotificationContent content = getContentFromRoomOrLegacy(notificationId);
|
||||||
|
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
Log.w(TAG, "DN|DISPLAY_ERR content_not_found id=" + notificationId);
|
// Content not found - likely removed during deduplication or cleanup
|
||||||
return Result.failure();
|
// Return success instead of failure to prevent retries for intentionally removed notifications
|
||||||
|
Log.w(TAG, "DN|DISPLAY_SKIP content_not_found id=" + notificationId + " (likely removed during deduplication)");
|
||||||
|
return Result.success(); // Success prevents retry loops for removed notifications
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if notification is ready to display
|
// Check if notification is ready to display
|
||||||
|
|||||||
@@ -298,9 +298,52 @@ public final class TimeSafariIntegrationManager {
|
|||||||
// 4) Convert bundle to NotificationContent[] and save/schedule
|
// 4) Convert bundle to NotificationContent[] and save/schedule
|
||||||
List<NotificationContent> contents = convertBundleToNotificationContent(bundle);
|
List<NotificationContent> contents = convertBundleToNotificationContent(bundle);
|
||||||
|
|
||||||
|
// Get existing notifications for duplicate checking
|
||||||
|
java.util.List<NotificationContent> existingNotifications = storage.getAllNotifications();
|
||||||
|
long toleranceMs = 60 * 1000; // 1 minute tolerance for DST shifts
|
||||||
|
java.util.Set<Long> batchScheduledTimes = new java.util.HashSet<>();
|
||||||
|
|
||||||
int scheduledCount = 0;
|
int scheduledCount = 0;
|
||||||
|
int skippedCount = 0;
|
||||||
for (NotificationContent content : contents) {
|
for (NotificationContent content : contents) {
|
||||||
try {
|
try {
|
||||||
|
// Check for duplicates within current batch
|
||||||
|
long scheduledTime = content.getScheduledTime();
|
||||||
|
boolean duplicateInBatch = false;
|
||||||
|
for (Long batchTime : batchScheduledTimes) {
|
||||||
|
if (Math.abs(batchTime - scheduledTime) <= toleranceMs) {
|
||||||
|
logger.w("TS: DUPLICATE_SKIP_BATCH id=" + content.getId() +
|
||||||
|
" scheduled_time=" + scheduledTime);
|
||||||
|
duplicateInBatch = true;
|
||||||
|
skippedCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateInBatch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicates in existing storage
|
||||||
|
boolean duplicateInStorage = false;
|
||||||
|
for (NotificationContent existing : existingNotifications) {
|
||||||
|
if (Math.abs(existing.getScheduledTime() - scheduledTime) <= toleranceMs) {
|
||||||
|
logger.w("TS: DUPLICATE_SKIP_STORAGE id=" + content.getId() +
|
||||||
|
" existing_id=" + existing.getId() +
|
||||||
|
" scheduled_time=" + scheduledTime);
|
||||||
|
duplicateInStorage = true;
|
||||||
|
skippedCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateInStorage) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark this scheduledTime as processed
|
||||||
|
batchScheduledTimes.add(scheduledTime);
|
||||||
|
|
||||||
// Save content first
|
// Save content first
|
||||||
storage.saveNotificationContent(content);
|
storage.saveNotificationContent(content);
|
||||||
// TTL validation happens inside scheduler.scheduleNotification()
|
// TTL validation happens inside scheduler.scheduleNotification()
|
||||||
@@ -312,8 +355,9 @@ public final class TimeSafariIntegrationManager {
|
|||||||
logger.w("TS: schedule/save failed for id=" + content.getId() + " " + perItem.getMessage());
|
logger.w("TS: schedule/save failed for id=" + content.getId() + " " + perItem.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.i("TS: fetchAndScheduleFromServer done; scheduled=" + scheduledCount + "/" + contents.size());
|
logger.i("TS: fetchAndScheduleFromServer done; scheduled=" + scheduledCount + "/" + contents.size() +
|
||||||
|
(skippedCount > 0 ? ", " + skippedCount + " duplicates skipped" : ""));
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.e("TS: fetchAndScheduleFromServer error", ex);
|
logger.e("TS: fetchAndScheduleFromServer error", ex);
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ export default defineConfigWithVueTs(
|
|||||||
'**/.gradle/**'
|
'**/.gradle/**'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
// Whitelist console usage in logger utility (single source of truth)
|
||||||
|
{
|
||||||
|
files: ['src/lib/logger.ts'],
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
pluginVue.configs['flat/essential'],
|
pluginVue.configs['flat/essential'],
|
||||||
vueTsConfigs.recommended,
|
vueTsConfigs.recommended,
|
||||||
)
|
)
|
||||||
|
|||||||
15
test-apps/daily-notification-test/package-lock.json
generated
15
test-apps/daily-notification-test/package-lock.json
generated
@@ -7,6 +7,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "daily-notification-test",
|
"name": "daily-notification-test",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^6.2.1",
|
"@capacitor/android": "^6.2.1",
|
||||||
"@capacitor/cli": "^6.2.1",
|
"@capacitor/cli": "^6.2.1",
|
||||||
@@ -116,6 +117,7 @@
|
|||||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.3",
|
"@babel/generator": "^7.28.3",
|
||||||
@@ -415,6 +417,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
|
||||||
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.28.4"
|
"@babel/types": "^7.28.4"
|
||||||
},
|
},
|
||||||
@@ -633,6 +636,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz",
|
||||||
"integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==",
|
"integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
@@ -2064,6 +2068,7 @@
|
|||||||
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
|
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.46.1",
|
"@typescript-eslint/scope-manager": "8.46.1",
|
||||||
"@typescript-eslint/types": "8.46.1",
|
"@typescript-eslint/types": "8.46.1",
|
||||||
@@ -2662,6 +2667,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2910,6 +2916,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.9",
|
"baseline-browser-mapping": "^2.8.9",
|
||||||
"caniuse-lite": "^1.0.30001746",
|
"caniuse-lite": "^1.0.30001746",
|
||||||
@@ -3372,6 +3379,7 @@
|
|||||||
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
|
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3433,6 +3441,7 @@
|
|||||||
"integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==",
|
"integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@@ -4292,6 +4301,7 @@
|
|||||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
@@ -5720,6 +5730,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -5797,6 +5808,7 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -6030,6 +6042,7 @@
|
|||||||
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
|
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -6300,6 +6313,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -6319,6 +6333,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
|
||||||
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
|
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.22",
|
"@vue/compiler-dom": "3.5.22",
|
||||||
"@vue/compiler-sfc": "3.5.22",
|
"@vue/compiler-sfc": "3.5.22",
|
||||||
|
|||||||
@@ -36,12 +36,76 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, toNative } from 'vue-facing-decorator'
|
import { Vue, Component, toNative } from 'vue-facing-decorator'
|
||||||
|
import { Capacitor } from '@capacitor/core'
|
||||||
|
import { DailyNotification } from '@timesafari/daily-notification-plugin'
|
||||||
|
import { TEST_USER_ZERO_CONFIG, generateEndorserJWT } from './config/test-user-zero'
|
||||||
|
import { logger } from './lib/logger'
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class App extends Vue {
|
class App extends Vue {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
errorMessage = ''
|
errorMessage = ''
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
// CRITICAL: Log immediately to verify this hook executes
|
||||||
|
console.log('🚀 App.vue: mounted() hook EXECUTING')
|
||||||
|
console.log('🚀 App.vue: Capacitor.isNativePlatform() =', Capacitor.isNativePlatform())
|
||||||
|
|
||||||
|
// Configure native fetcher on native platforms (Android/iOS)
|
||||||
|
if (Capacitor.isNativePlatform()) {
|
||||||
|
console.log('🚀 App.vue: Entering native platform configuration block')
|
||||||
|
try {
|
||||||
|
// Use console.log for visibility in adb logcat
|
||||||
|
console.log('🔧 App.vue: Starting native fetcher configuration...')
|
||||||
|
logger.info('Configuring native fetcher...')
|
||||||
|
|
||||||
|
// Get API server URL first (before JWT generation)
|
||||||
|
const apiBaseUrl = TEST_USER_ZERO_CONFIG.getApiServerUrl()
|
||||||
|
console.log('🔧 App.vue: API Base URL:', apiBaseUrl)
|
||||||
|
console.log('🔧 App.vue: Server Mode:', TEST_USER_ZERO_CONFIG.api.serverMode)
|
||||||
|
|
||||||
|
// Skip configuration if in mock mode (no real API calls)
|
||||||
|
if (TEST_USER_ZERO_CONFIG.api.serverMode === 'mock') {
|
||||||
|
console.log('⏭️ App.vue: Mock mode - skipping configuration')
|
||||||
|
logger.warn('Mock mode enabled - native fetcher will not be configured')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔧 App.vue: Generating JWT token...')
|
||||||
|
// Generate JWT token for authentication
|
||||||
|
const jwtToken = await generateEndorserJWT()
|
||||||
|
console.log('✅ App.vue: JWT token generated, length:', jwtToken.length)
|
||||||
|
|
||||||
|
console.log('🔧 App.vue: Calling configureNativeFetcher with:', {
|
||||||
|
apiBaseUrl,
|
||||||
|
activeDid: TEST_USER_ZERO_CONFIG.identity.did.substring(0, 30) + '...',
|
||||||
|
jwtTokenLength: jwtToken.length
|
||||||
|
})
|
||||||
|
|
||||||
|
// Configure native fetcher with credentials
|
||||||
|
await DailyNotification.configureNativeFetcher({
|
||||||
|
apiBaseUrl: apiBaseUrl,
|
||||||
|
activeDid: TEST_USER_ZERO_CONFIG.identity.did,
|
||||||
|
jwtToken: jwtToken
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('✅ App.vue: Native fetcher configured successfully!')
|
||||||
|
logger.info('Native fetcher configured successfully', {
|
||||||
|
apiBaseUrl: apiBaseUrl.substring(0, 50) + '...',
|
||||||
|
activeDid: TEST_USER_ZERO_CONFIG.identity.did.substring(0, 30) + '...'
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ App.vue: Failed to configure native fetcher:', error)
|
||||||
|
console.error('❌ App.vue: Error details:', error instanceof Error ? error.stack : String(error))
|
||||||
|
logger.error('Failed to configure native fetcher:', error)
|
||||||
|
this.errorMessage = `Failed to configure native fetcher: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('⏭️ App.vue: Web platform - skipping configuration')
|
||||||
|
logger.info('Web platform detected - native fetcher configuration skipped')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clearError() {
|
clearError() {
|
||||||
this.errorMessage = ''
|
this.errorMessage = ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,17 @@
|
|||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Lazy import logger to avoid ES module issues when loaded by Capacitor CLI (CommonJS)
|
||||||
|
// Logger is only used inside functions, not at module scope
|
||||||
|
let logger: { error: (...args: unknown[]) => void; custom: (emoji: string, ...args: unknown[]) => void } | null = null;
|
||||||
|
const getLogger = async () => {
|
||||||
|
if (!logger) {
|
||||||
|
const loggerModule = await import('../lib/logger');
|
||||||
|
logger = loggerModule.logger;
|
||||||
|
}
|
||||||
|
return logger;
|
||||||
|
};
|
||||||
|
|
||||||
export const TEST_USER_ZERO_CONFIG = {
|
export const TEST_USER_ZERO_CONFIG = {
|
||||||
// User Zero Identity (from crowd-master testUtils.ts)
|
// User Zero Identity (from crowd-master testUtils.ts)
|
||||||
identity: {
|
identity: {
|
||||||
@@ -25,7 +36,7 @@ export const TEST_USER_ZERO_CONFIG = {
|
|||||||
// - "production": Use production API server
|
// - "production": Use production API server
|
||||||
// - "mock": Use mock responses (no network calls)
|
// - "mock": Use mock responses (no network calls)
|
||||||
// - "custom": Use servers.custom URL
|
// - "custom": Use servers.custom URL
|
||||||
serverMode: "mock" as "localhost" | "staging" | "production" | "mock" | "custom",
|
serverMode: "localhost" as "localhost" | "staging" | "production" | "mock" | "custom",
|
||||||
|
|
||||||
// Server URLs for different modes
|
// Server URLs for different modes
|
||||||
servers: {
|
servers: {
|
||||||
@@ -258,7 +269,8 @@ export async function generateEndorserJWT(): Promise<string> {
|
|||||||
|
|
||||||
return jwt;
|
return jwt;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to generate ES256K JWT:', error);
|
const log = await getLogger();
|
||||||
|
log.error('Failed to generate ES256K JWT:', error);
|
||||||
throw new Error(`JWT generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
throw new Error(`JWT generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,10 +323,10 @@ export class TestUserZeroAPI {
|
|||||||
/**
|
/**
|
||||||
* Set API server URL (useful for switching between localhost, staging, etc.)
|
* Set API server URL (useful for switching between localhost, staging, etc.)
|
||||||
*/
|
*/
|
||||||
setBaseUrl(url: string): void {
|
async setBaseUrl(url: string): Promise<void> {
|
||||||
this.baseUrl = url;
|
this.baseUrl = url;
|
||||||
// eslint-disable-next-line no-console
|
const log = await getLogger();
|
||||||
console.log("🔧 API base URL updated to:", url);
|
log.custom("🔧", "API base URL updated to:", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -331,8 +343,8 @@ export class TestUserZeroAPI {
|
|||||||
|
|
||||||
if (useMock) {
|
if (useMock) {
|
||||||
// Return mock data for offline testing
|
// Return mock data for offline testing
|
||||||
// eslint-disable-next-line no-console
|
const log = await getLogger();
|
||||||
console.log("🧪 Using mock starred projects response");
|
log.custom("🧪", "Using mock starred projects response");
|
||||||
return MOCK_STARRED_PROJECTS_RESPONSE;
|
return MOCK_STARRED_PROJECTS_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,10 +362,9 @@ export class TestUserZeroAPI {
|
|||||||
afterId: afterId || TEST_USER_ZERO_CONFIG.starredProjects.lastAckedJwtId
|
afterId: afterId || TEST_USER_ZERO_CONFIG.starredProjects.lastAckedJwtId
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
const log = await getLogger();
|
||||||
console.log("🌐 Making real API call to:", url);
|
log.custom("🌐", "Making real API call to:", url);
|
||||||
// eslint-disable-next-line no-console
|
log.custom("📦", "Request body:", requestBody);
|
||||||
console.log("📦 Request body:", requestBody);
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -371,10 +382,10 @@ export class TestUserZeroAPI {
|
|||||||
/**
|
/**
|
||||||
* Refresh JWT token
|
* Refresh JWT token
|
||||||
*/
|
*/
|
||||||
refreshToken(): void {
|
async refreshToken(): Promise<void> {
|
||||||
this.jwt = generateTestJWT();
|
this.jwt = generateTestJWT();
|
||||||
// eslint-disable-next-line no-console
|
const log = await getLogger();
|
||||||
console.log("🔄 JWT token refreshed");
|
log.custom("🔄", "JWT token refreshed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
152
test-apps/daily-notification-test/src/lib/logger.ts
Normal file
152
test-apps/daily-notification-test/src/lib/logger.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Logger Utility
|
||||||
|
*
|
||||||
|
* Centralized logging utility that wraps console methods with ESLint suppression
|
||||||
|
* in a single location. Provides structured logging with log levels and optional
|
||||||
|
* emoji prefixes for visual distinction.
|
||||||
|
*
|
||||||
|
* This file is the single whitelisted location for console usage in the application.
|
||||||
|
* All console methods are allowed here as this is the designated logging utility.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum LogLevel {
|
||||||
|
DEBUG = 0,
|
||||||
|
INFO = 1,
|
||||||
|
WARN = 2,
|
||||||
|
ERROR = 3,
|
||||||
|
NONE = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoggerConfig {
|
||||||
|
level: LogLevel
|
||||||
|
enableEmojis: boolean
|
||||||
|
prefix?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
private config: LoggerConfig
|
||||||
|
|
||||||
|
constructor(config: Partial<LoggerConfig> = {}) {
|
||||||
|
this.config = {
|
||||||
|
level: config.level ?? LogLevel.DEBUG,
|
||||||
|
enableEmojis: config.enableEmojis ?? true,
|
||||||
|
prefix: config.prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update logger configuration
|
||||||
|
*/
|
||||||
|
configure(config: Partial<LoggerConfig>): void {
|
||||||
|
this.config = { ...this.config, ...config }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log debug message
|
||||||
|
*/
|
||||||
|
debug(...args: unknown[]): void {
|
||||||
|
if (this.config.level <= LogLevel.DEBUG) {
|
||||||
|
this.logInternal('🐛', 'DEBUG', args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log info message
|
||||||
|
*/
|
||||||
|
info(...args: unknown[]): void {
|
||||||
|
if (this.config.level <= LogLevel.INFO) {
|
||||||
|
this.logInternal('ℹ️', 'INFO', args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log warning message
|
||||||
|
*/
|
||||||
|
warn(...args: unknown[]): void {
|
||||||
|
if (this.config.level <= LogLevel.WARN) {
|
||||||
|
this.logInternal('⚠️', 'WARN', args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log error message
|
||||||
|
*/
|
||||||
|
error(...args: unknown[]): void {
|
||||||
|
if (this.config.level <= LogLevel.ERROR) {
|
||||||
|
this.logInternal('❌', 'ERROR', args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log message without emoji (for custom formatting)
|
||||||
|
*/
|
||||||
|
log(...args: unknown[]): void {
|
||||||
|
if (this.config.level <= LogLevel.INFO) {
|
||||||
|
console.log(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal logging method with emoji and level support
|
||||||
|
*/
|
||||||
|
private logInternal(emoji: string, level: string, args: unknown[]): void {
|
||||||
|
const prefix = this.config.prefix ? `[${this.config.prefix}] ` : ''
|
||||||
|
const emojiPrefix = this.config.enableEmojis ? `${emoji} ` : ''
|
||||||
|
const levelPrefix = `[${level}] `
|
||||||
|
|
||||||
|
console.log(`${emojiPrefix}${prefix}${levelPrefix}`, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log with custom emoji (convenience method)
|
||||||
|
*/
|
||||||
|
custom(emoji: string, ...args: unknown[]): void {
|
||||||
|
if (this.config.level <= LogLevel.INFO) {
|
||||||
|
const prefix = this.config.prefix ? `[${this.config.prefix}] ` : ''
|
||||||
|
const emojiPrefix = this.config.enableEmojis ? `${emoji} ` : ''
|
||||||
|
|
||||||
|
console.log(`${emojiPrefix}${prefix}`, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log grouped message (uses console.group)
|
||||||
|
*/
|
||||||
|
group(label: string): void {
|
||||||
|
if (this.config.level <= LogLevel.INFO) {
|
||||||
|
console.group(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End log group
|
||||||
|
*/
|
||||||
|
groupEnd(): void {
|
||||||
|
if (this.config.level <= LogLevel.INFO) {
|
||||||
|
console.groupEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log table (uses console.table)
|
||||||
|
*/
|
||||||
|
table(data: unknown): void {
|
||||||
|
if (this.config.level <= LogLevel.DEBUG) {
|
||||||
|
console.table(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
export const logger = new Logger({
|
||||||
|
level: import.meta.env.DEV ? LogLevel.DEBUG : LogLevel.INFO,
|
||||||
|
enableEmojis: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Export class for creating custom logger instances
|
||||||
|
export { Logger }
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { logger } from '../lib/logger'
|
||||||
import HomeView from '../views/HomeView.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -105,16 +106,14 @@ router.beforeEach((to, from, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add loading state
|
// Add loading state
|
||||||
// eslint-disable-next-line no-console
|
logger.custom("🔄", `Navigating from ${String(from.name) || 'unknown'} to ${String(to.name) || 'unknown'}`)
|
||||||
console.log(`🔄 Navigating from ${String(from.name) || 'unknown'} to ${String(to.name) || 'unknown'}`)
|
|
||||||
|
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
router.afterEach((to) => {
|
router.afterEach((to) => {
|
||||||
// Clear any previous errors on successful navigation
|
// Clear any previous errors on successful navigation
|
||||||
// eslint-disable-next-line no-console
|
logger.custom("✅", `Navigation completed: ${String(to.name) || 'unknown'}`)
|
||||||
console.log(`✅ Navigation completed: ${String(to.name) || 'unknown'}`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -4,12 +4,16 @@
|
|||||||
* Pinia store for managing global app state, loading, and errors
|
* Pinia store for managing global app state, loading, and errors
|
||||||
* Platform-neutral design for Android/iOS/Electron
|
* Platform-neutral design for Android/iOS/Electron
|
||||||
*
|
*
|
||||||
|
* Note: This file uses the centralized logger utility (logger) instead of
|
||||||
|
* direct console usage. Console usage is whitelisted only in src/lib/logger.ts
|
||||||
|
*
|
||||||
* @author Matthew Raymer
|
* @author Matthew Raymer
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import { logger } from '../lib/logger'
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
@@ -52,7 +56,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
|
|
||||||
function setError(message: string): void {
|
function setError(message: string): void {
|
||||||
errorMessage.value = message
|
errorMessage.value = message
|
||||||
console.error('App Error:', message)
|
logger.error('App Error:', message)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearError(): void {
|
function clearError(): void {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ import { useRouter } from 'vue-router'
|
|||||||
import { useAppStore } from '@/stores/app'
|
import { useAppStore } from '@/stores/app'
|
||||||
import ActionCard from '@/components/cards/ActionCard.vue'
|
import ActionCard from '@/components/cards/ActionCard.vue'
|
||||||
import StatusCard from '@/components/cards/StatusCard.vue'
|
import StatusCard from '@/components/cards/StatusCard.vue'
|
||||||
import { TEST_USER_ZERO_CONFIG, generateEndorserJWT } from '@/config/test-user-zero'
|
// Note: Native fetcher configuration moved to App.vue mounted() hook
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
@@ -430,54 +430,10 @@ const openConsole = (): void => {
|
|||||||
alert('📖 Console Logs\n\nOpen your browser\'s Developer Tools (F12) and check the Console tab for detailed diagnostic information.')
|
alert('📖 Console Logs\n\nOpen your browser\'s Developer Tools (F12) and check the Console tab for detailed diagnostic information.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure native fetcher with test user zero credentials
|
|
||||||
const configureNativeFetcher = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const { Capacitor } = await import('@capacitor/core')
|
|
||||||
const isNative = Capacitor.isNativePlatform()
|
|
||||||
|
|
||||||
if (isNative) {
|
|
||||||
const { DailyNotification } = await import('@timesafari/daily-notification-plugin')
|
|
||||||
|
|
||||||
// Get API server URL based on mode and platform
|
|
||||||
const apiBaseUrl = TEST_USER_ZERO_CONFIG.getApiServerUrl()
|
|
||||||
|
|
||||||
// Only configure if not in mock mode
|
|
||||||
if (TEST_USER_ZERO_CONFIG.api.serverMode !== 'mock' && apiBaseUrl !== 'mock://localhost') {
|
|
||||||
console.log('🔧 Configuring native fetcher with:', {
|
|
||||||
apiBaseUrl,
|
|
||||||
activeDid: TEST_USER_ZERO_CONFIG.identity.did.substring(0, 30) + '...',
|
|
||||||
serverMode: TEST_USER_ZERO_CONFIG.api.serverMode
|
|
||||||
})
|
|
||||||
|
|
||||||
// Generate ES256K JWT token using did-jwt library
|
|
||||||
// This mimics TimeSafari's createEndorserJwtForKey() function
|
|
||||||
// In production TimeSafari app, this would use:
|
|
||||||
// const account = await retrieveFullyDecryptedAccount(activeDid);
|
|
||||||
// const jwtToken = await createEndorserJwtForKey(account, {...});
|
|
||||||
const jwtToken = await generateEndorserJWT()
|
|
||||||
|
|
||||||
await DailyNotification.configureNativeFetcher({
|
|
||||||
apiBaseUrl: apiBaseUrl,
|
|
||||||
activeDid: TEST_USER_ZERO_CONFIG.identity.did,
|
|
||||||
jwtToken: jwtToken // Pre-generated token (ES256K signed in production)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('✅ Native fetcher configured successfully')
|
|
||||||
} else {
|
|
||||||
console.log('⏭️ Skipping native fetcher configuration (mock mode enabled)')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to configure native fetcher:', error)
|
|
||||||
// Don't block app initialization if configuration fails
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize system status when component mounts
|
// Initialize system status when component mounts
|
||||||
|
// Note: Native fetcher configuration is handled in App.vue mounted() hook
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log('🏠 HomeView mounted - checking initial system status...')
|
console.log('🏠 HomeView mounted - checking initial system status...')
|
||||||
await configureNativeFetcher()
|
|
||||||
await checkSystemStatus()
|
await checkSystemStatus()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ async function testJWTGeneration() {
|
|||||||
|
|
||||||
// Generate test JWT
|
// Generate test JWT
|
||||||
// Generate a fresh JWT token
|
// Generate a fresh JWT token
|
||||||
apiClient.refreshToken()
|
await apiClient.refreshToken()
|
||||||
const jwt = apiClient.getJWT() // Get the JWT from the client
|
const jwt = apiClient.getJWT() // Get the JWT from the client
|
||||||
|
|
||||||
console.log('✅ JWT generation successful')
|
console.log('✅ JWT generation successful')
|
||||||
|
|||||||
Reference in New Issue
Block a user