forked from trent_larson/crowd-funder-for-time-pwa
Merge branch 'master' into logger-level
This commit is contained in:
@@ -8,8 +8,10 @@ This guide explains how to build TimeSafari for different platforms using the co
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 🖥️ Web Development
|
# 🖥️ Web Development
|
||||||
npm run build:web:dev # Start development server with hot reload
|
npm install # setup -- and pkgx.dev `dev` command before this will set environment with npm, etc
|
||||||
npm run build:web:prod # Production build
|
npm run build:web:serve -- --test # Start with test endorser server
|
||||||
|
npm run build:web:dev # Start development server with hot reload with local endorser server
|
||||||
|
npm run build:web:prod # Production build
|
||||||
|
|
||||||
# 📱 Mobile Development
|
# 📱 Mobile Development
|
||||||
npm run build:ios # iOS build (opens Xcode)
|
npm run build:ios # iOS build (opens Xcode)
|
||||||
@@ -2401,4 +2403,4 @@ All scripts use consistent error handling:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Note**: This documentation is maintained alongside the build system. For the most up-to-date information, refer to the actual script files and Vite configuration files in the repository.
|
**Note**: This documentation is maintained alongside the build system. For the most up-to-date information, refer to the actual script files and Vite configuration files in the repository.
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -3,36 +3,9 @@
|
|||||||
[Time Safari](https://timesafari.org/) allows people to ease into collaboration: start with expressions of gratitude
|
[Time Safari](https://timesafari.org/) allows people to ease into collaboration: start with expressions of gratitude
|
||||||
and expand to crowd-fund with time & money, then record and see the impact of contributions.
|
and expand to crowd-fund with time & money, then record and see the impact of contributions.
|
||||||
|
|
||||||
## Database Migration Status
|
|
||||||
|
|
||||||
**Current Status**: The application is undergoing a migration from Dexie (IndexedDB) to SQLite using absurd-sql. This migration is in **Phase 2** with a well-defined migration fence in place.
|
|
||||||
|
|
||||||
### Migration Progress
|
|
||||||
- ✅ **SQLite Database Service**: Fully implemented with absurd-sql
|
|
||||||
- ✅ **Platform Service Layer**: Unified database interface across platforms
|
|
||||||
- ✅ **Settings Migration**: Core user settings transferred
|
|
||||||
- ✅ **Account Migration**: Identity and key management
|
|
||||||
- 🔄 **Contact Migration**: User contact data (via import interface)
|
|
||||||
- 📋 **Code Cleanup**: Remove unused Dexie imports
|
|
||||||
|
|
||||||
### Migration Fence
|
|
||||||
The migration is controlled by a **migration fence** that separates legacy Dexie code from the new SQLite implementation. See [Migration Fence Definition](doc/migration-fence-definition.md) for complete details.
|
|
||||||
|
|
||||||
**Key Points**:
|
|
||||||
- Legacy Dexie database is disabled by default
|
|
||||||
- All database operations go through `PlatformServiceMixin`
|
|
||||||
- Migration tools provide controlled access to both databases
|
|
||||||
- Clear separation between legacy and new code
|
|
||||||
|
|
||||||
### Migration Documentation
|
|
||||||
- [Migration Guide](doc/migration-to-wa-sqlite.md) - Complete migration process
|
|
||||||
- [Migration Fence Definition](doc/migration-fence-definition.md) - Fence boundaries and rules
|
|
||||||
- [Database Migration Guide](doc/database-migration-guide.md) - User-facing migration tools
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
See [project.task.yaml](project.task.yaml) for current priorities.
|
See [ClickUp](https://sharing.clickup.com/9014278710/l/h/8cmnyhp-174/10573fec74e2ba0) for current priorities.
|
||||||
(Numbers at the beginning of lines are estimated hours. See [taskyaml.org](https://taskyaml.org/) for details.)
|
|
||||||
|
|
||||||
## Setup & Building
|
## Setup & Building
|
||||||
|
|
||||||
@@ -42,14 +15,16 @@ Quick start:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
npm run build:web:serve -- --test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To be able to make submissions: go to "profile" (bottom left), go to the bottom and expand "Show Advanced Settings", go to the bottom and to the "Test Page", and finally "Become User 0" to see all the functionality.
|
||||||
|
|
||||||
See [BUILDING.md](BUILDING.md) for comprehensive build instructions for all platforms (Web, Electron, iOS, Android, Docker).
|
See [BUILDING.md](BUILDING.md) for comprehensive build instructions for all platforms (Web, Electron, iOS, Android, Docker).
|
||||||
|
|
||||||
## Development Database Clearing
|
## Development Database Clearing
|
||||||
|
|
||||||
TimeSafari provides a simple script-based approach to clear the database for development purposes.
|
TimeSafari provides a simple script-based approach to clear the local database (not the claim server) for development purposes.
|
||||||
|
|
||||||
## Logging Configuration
|
## Logging Configuration
|
||||||
|
|
||||||
@@ -155,7 +130,6 @@ const apiUrl = `${APP_SERVER}/api/claim/123`;
|
|||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- [Domain Configuration System](docs/domain-configuration.md) - Complete guide
|
|
||||||
- [Constants and Configuration](src/constants/app.ts) - Core constants
|
- [Constants and Configuration](src/constants/app.ts) - Core constants
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7-beta",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor-community/electron": "^5.0.1",
|
"@capacitor-community/electron": "^5.0.1",
|
||||||
"@capacitor-community/sqlite": "6.0.2",
|
"@capacitor-community/sqlite": "6.0.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7-beta",
|
||||||
"description": "Time Safari Application",
|
"description": "Time Safari Application",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Time Safari Team"
|
"name": "Time Safari Team"
|
||||||
|
|||||||
@@ -73,7 +73,6 @@
|
|||||||
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
||||||
:src="record.image"
|
:src="record.image"
|
||||||
alt="Activity image"
|
alt="Activity image"
|
||||||
@load="handleImageLoad(record.image)"
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,13 +271,6 @@ export default class ActivityListItem extends Vue {
|
|||||||
@Prop() isRegistered!: boolean;
|
@Prop() isRegistered!: boolean;
|
||||||
@Prop() activeDid!: string;
|
@Prop() activeDid!: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Function prop for handling image caching
|
|
||||||
* Called when an image loads successfully, allowing parent to control caching behavior
|
|
||||||
*/
|
|
||||||
@Prop({ type: Function, default: () => {} })
|
|
||||||
onImageCache!: (imageUrl: string) => void | Promise<void>;
|
|
||||||
|
|
||||||
isHiddenDid = isHiddenDid;
|
isHiddenDid = isHiddenDid;
|
||||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
$notify!: (notification: any, timeout?: number) => void;
|
$notify!: (notification: any, timeout?: number) => void;
|
||||||
@@ -295,14 +287,6 @@ export default class ActivityListItem extends Vue {
|
|||||||
this.notify.warning(NOTIFY_UNKNOWN_PERSON.message, TIMEOUTS.STANDARD);
|
this.notify.warning(NOTIFY_UNKNOWN_PERSON.message, TIMEOUTS.STANDARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle image load event - call function prop for caching
|
|
||||||
* Allows parent to control caching behavior and validation
|
|
||||||
*/
|
|
||||||
handleImageLoad(imageUrl: string): void {
|
|
||||||
this.onImageCache(imageUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fetchAmount(): string {
|
get fetchAmount(): string {
|
||||||
const claim =
|
const claim =
|
||||||
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
|
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export default class ContactInputForm extends Vue {
|
|||||||
*/
|
*/
|
||||||
@Emit("qr-scan")
|
@Emit("qr-scan")
|
||||||
private handleQRScan(): void {
|
private handleQRScan(): void {
|
||||||
console.log("[ContactInputForm] QR scan button clicked");
|
// QR scan button clicked - event emitted for parent handling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import { logger } from "../utils/logger";
|
|||||||
@Component({ emits: ["update:isOpen"] })
|
@Component({ emits: ["update:isOpen"] })
|
||||||
export default class ImageViewer extends Vue {
|
export default class ImageViewer extends Vue {
|
||||||
@Prop() imageUrl!: string;
|
@Prop() imageUrl!: string;
|
||||||
@Prop() imageData!: Blob | null;
|
|
||||||
@Prop() isOpen!: boolean;
|
@Prop() isOpen!: boolean;
|
||||||
|
|
||||||
userAgent = new UAParser();
|
userAgent = new UAParser();
|
||||||
|
|||||||
@@ -1348,12 +1348,12 @@ export async function createEndorserJwtVcFromClaim(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a JWT for a RegisterAction claim.
|
* Create a JWT for a RegisterAction claim, used for registrations & invites.
|
||||||
*
|
*
|
||||||
* @param activeDid - The DID of the user creating the invite
|
* @param activeDid - The DID of the user creating the invite
|
||||||
* @param contact - The contact to register, with a 'did' field (all optional for invites)
|
* @param contact - Optional - The contact to register, with a 'did' field (all optional for invites)
|
||||||
* @param identifier - The identifier for the invite, usually random
|
* @param identifier - Optional - The identifier for the invite, usually random
|
||||||
* @param expiresIn - The number of seconds until the invite expires
|
* @param expiresIn - Optional - The number of seconds until the invite expires
|
||||||
* @returns The JWT for the RegisterAction claim
|
* @returns The JWT for the RegisterAction claim
|
||||||
*/
|
*/
|
||||||
export async function createInviteJwt(
|
export async function createInviteJwt(
|
||||||
@@ -1367,7 +1367,7 @@ export async function createInviteJwt(
|
|||||||
"@type": "RegisterAction",
|
"@type": "RegisterAction",
|
||||||
agent: { identifier: activeDid },
|
agent: { identifier: activeDid },
|
||||||
object: SERVICE_ID,
|
object: SERVICE_ID,
|
||||||
identifier: identifier,
|
identifier: identifier, // not sent if undefined
|
||||||
};
|
};
|
||||||
if (contact?.did) {
|
if (contact?.did) {
|
||||||
vcClaim.participant = { identifier: contact.did };
|
vcClaim.participant = { identifier: contact.did };
|
||||||
|
|||||||
@@ -82,6 +82,15 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "database-migration",
|
name: "database-migration",
|
||||||
component: () => import("../views/DatabaseMigration.vue"),
|
component: () => import("../views/DatabaseMigration.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/deep-link-error",
|
||||||
|
name: "deep-link-error",
|
||||||
|
component: () => import("../views/DeepLinkErrorView.vue"),
|
||||||
|
meta: {
|
||||||
|
title: "Invalid Deep Link",
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/did/:did?",
|
path: "/did/:did?",
|
||||||
name: "did",
|
name: "did",
|
||||||
@@ -276,15 +285,6 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "user-profile",
|
name: "user-profile",
|
||||||
component: () => import("../views/UserProfileView.vue"),
|
component: () => import("../views/UserProfileView.vue"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/deep-link-error",
|
|
||||||
name: "deep-link-error",
|
|
||||||
component: () => import("../views/DeepLinkErrorView.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "Invalid Deep Link",
|
|
||||||
requiresAuth: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const isElectron = window.location.protocol === "file:";
|
const isElectron = window.location.protocol === "file:";
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
// **WORKER-COMPATIBLE CRYPTO POLYFILL**: Must be at the very top
|
// **WORKER-COMPATIBLE CRYPTO POLYFILL**: Must be at the very top
|
||||||
// This prevents "crypto is not defined" errors when running in worker context
|
// This prevents "crypto is not defined" errors when running in worker context
|
||||||
if (typeof window === "undefined" && typeof crypto === "undefined") {
|
if (typeof window === "undefined" && typeof crypto === "undefined") {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(globalThis as any).crypto = {
|
(globalThis as any).crypto = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
getRandomValues: (array: any) => {
|
getRandomValues: (array: any) => {
|
||||||
// Simple fallback for worker context
|
// Simple fallback for worker context
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import {
|
|||||||
DeepLinkRoute,
|
DeepLinkRoute,
|
||||||
} from "../interfaces/deepLinks";
|
} from "../interfaces/deepLinks";
|
||||||
import type { DeepLinkError } from "../interfaces/deepLinks";
|
import type { DeepLinkError } from "../interfaces/deepLinks";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
// Helper function to extract the first key from a Zod object schema
|
// Helper function to extract the first key from a Zod object schema
|
||||||
function getFirstKeyFromZodObject(
|
function getFirstKeyFromZodObject(
|
||||||
@@ -178,7 +179,7 @@ export class DeepLinkHandler {
|
|||||||
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
||||||
routeName = ROUTE_MAP[validRoute].name;
|
routeName = ROUTE_MAP[validRoute].name;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[DeepLink] Invalid route path: ${path}`);
|
logger.error(`[DeepLink] Invalid route path: ${path}`);
|
||||||
|
|
||||||
// Redirect to error page with information about the invalid link
|
// Redirect to error page with information about the invalid link
|
||||||
await this.router.replace({
|
await this.router.replace({
|
||||||
@@ -204,9 +205,8 @@ export class DeepLinkHandler {
|
|||||||
validatedParams = await schema.parseAsync(params);
|
validatedParams = await schema.parseAsync(params);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// For parameter validation errors, provide specific error feedback
|
// For parameter validation errors, provide specific error feedback
|
||||||
logConsoleAndDb(
|
logger.error(
|
||||||
`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`,
|
`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`,
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
await this.router.replace({
|
await this.router.replace({
|
||||||
name: "deep-link-error",
|
name: "deep-link-error",
|
||||||
@@ -229,9 +229,8 @@ export class DeepLinkHandler {
|
|||||||
params: validatedParams,
|
params: validatedParams,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleAndDb(
|
logger.error(
|
||||||
`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)}`,
|
`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)}`,
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
// For parameter validation errors, provide specific error feedback
|
// For parameter validation errors, provide specific error feedback
|
||||||
await this.router.replace({
|
await this.router.replace({
|
||||||
@@ -263,9 +262,8 @@ export class DeepLinkHandler {
|
|||||||
await this.validateAndRoute(path, sanitizedParams, query);
|
await this.validateAndRoute(path, sanitizedParams, query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const deepLinkError = error as DeepLinkError;
|
const deepLinkError = error as DeepLinkError;
|
||||||
logConsoleAndDb(
|
logger.error(
|
||||||
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
|
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
throw {
|
throw {
|
||||||
|
|||||||
@@ -693,7 +693,8 @@ export class WebPlatformService implements PlatformService {
|
|||||||
const setClause = keys.map((key) => `${key} = ?`).join(", ");
|
const setClause = keys.map((key) => `${key} = ?`).join(", ");
|
||||||
const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
|
const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
|
||||||
const params = [...keys.map((key) => settings[key]), did];
|
const params = [...keys.map((key) => settings[key]), did];
|
||||||
console.log(
|
// Log update operation for debugging
|
||||||
|
logger.debug(
|
||||||
"[WebPlatformService] updateDidSpecificSettings",
|
"[WebPlatformService] updateDidSpecificSettings",
|
||||||
sql,
|
sql,
|
||||||
JSON.stringify(params, null, 2),
|
JSON.stringify(params, null, 2),
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|||||||
})
|
})
|
||||||
export default class PlatformServiceMixinTest extends Vue {
|
export default class PlatformServiceMixinTest extends Vue {
|
||||||
result: string = "";
|
result: string = "";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
userZeroTestResult: any = null;
|
userZeroTestResult: any = null;
|
||||||
activeTest: string = ""; // Track which test is currently active
|
activeTest: string = ""; // Track which test is currently active
|
||||||
|
|
||||||
@@ -267,6 +268,7 @@ This tests the complete save → retrieve cycle with actual database interaction
|
|||||||
this.result = `User #0 settings test completed. isRegistered: ${accountSettings.isRegistered}`;
|
this.result = `User #0 settings test completed. isRegistered: ${accountSettings.isRegistered}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.result = `Error testing User #0 settings: ${error}`;
|
this.result = `Error testing User #0 settings: ${error}`;
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error("Error testing User #0 settings:", error);
|
console.error("Error testing User #0 settings:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,29 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import { SERVICE_ID } from "../libs/endorserServer";
|
import { SERVICE_ID } from "../libs/endorserServer";
|
||||||
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
import {
|
||||||
|
DEFAULT_ROOT_DERIVATION_PATH,
|
||||||
|
deriveAddress,
|
||||||
|
newIdentifier,
|
||||||
|
} from "../libs/crypto";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { AppString } from "../constants/app";
|
import { AppString } from "../constants/app";
|
||||||
|
import { saveNewIdentity } from "@/libs/util";
|
||||||
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||||
|
|
||||||
|
const TEST_USER_0_MNEMONIC =
|
||||||
|
"rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage";
|
||||||
|
|
||||||
|
export async function testBecomeUser0() {
|
||||||
|
const [addr, privateHex, publicHex, deriPath] = deriveAddress(TEST_USER_0_MNEMONIC);
|
||||||
|
|
||||||
|
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
|
||||||
|
await saveNewIdentity(identity0, TEST_USER_0_MNEMONIC, DEFAULT_ROOT_DERIVATION_PATH);
|
||||||
|
const platformService = await PlatformServiceFactory.getInstance();
|
||||||
|
await platformService.updateDidSpecificSettings(identity0.did, {
|
||||||
|
isRegistered: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get User #0 to sign & submit a RegisterAction for the user's activeDid.
|
* Get User #0 to sign & submit a RegisterAction for the user's activeDid.
|
||||||
@@ -15,10 +35,7 @@ import { AppString } from "../constants/app";
|
|||||||
* @throws Error if registration fails or database access fails
|
* @throws Error if registration fails or database access fails
|
||||||
*/
|
*/
|
||||||
export async function testServerRegisterUser() {
|
export async function testServerRegisterUser() {
|
||||||
const testUser0Mnem =
|
const [addr, privateHex, publicHex, deriPath] = deriveAddress(TEST_USER_0_MNEMONIC);
|
||||||
"seminar accuse mystery assist delay law thing deal image undo guard initial shallow wrestle list fragile borrow velvet tomorrow awake explain test offer control";
|
|
||||||
|
|
||||||
const [addr, privateHex, publicHex, deriPath] = deriveAddress(testUser0Mnem);
|
|
||||||
|
|
||||||
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
|
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
|
||||||
|
|
||||||
@@ -32,9 +49,9 @@ export async function testServerRegisterUser() {
|
|||||||
const vcClaim = {
|
const vcClaim = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "RegisterAction",
|
"@type": "RegisterAction",
|
||||||
agent: { did: identity0.did },
|
agent: { identifier: identity0.did },
|
||||||
object: SERVICE_ID,
|
object: SERVICE_ID,
|
||||||
participant: { did: settings.activeDid },
|
participant: { identifier: settings.activeDid },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make a payload for the claim
|
// Make a payload for the claim
|
||||||
@@ -71,4 +88,5 @@ export async function testServerRegisterUser() {
|
|||||||
|
|
||||||
const resp = await axios.post(url, payload, { headers });
|
const resp = await axios.post(url, payload, { headers });
|
||||||
logger.log("User registration result:", resp);
|
logger.log("User registration result:", resp);
|
||||||
|
return resp;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ export const PlatformServiceMixin = {
|
|||||||
* Used for change detection and component updates
|
* Used for change detection and component updates
|
||||||
*/
|
*/
|
||||||
currentActiveDid(): string | null {
|
currentActiveDid(): string | null {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (this as any)._currentActiveDid;
|
return (this as any)._currentActiveDid;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -200,7 +201,9 @@ export const PlatformServiceMixin = {
|
|||||||
* This method should be called when the user switches identities
|
* This method should be called when the user switches identities
|
||||||
*/
|
*/
|
||||||
async $updateActiveDid(newDid: string | null): Promise<void> {
|
async $updateActiveDid(newDid: string | null): Promise<void> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const oldDid = (this as any)._currentActiveDid;
|
const oldDid = (this as any)._currentActiveDid;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(this as any)._currentActiveDid = newDid;
|
(this as any)._currentActiveDid = newDid;
|
||||||
|
|
||||||
if (newDid !== oldDid) {
|
if (newDid !== oldDid) {
|
||||||
@@ -291,6 +294,7 @@ export const PlatformServiceMixin = {
|
|||||||
|
|
||||||
// Convert searchBoxes array to JSON string if present
|
// Convert searchBoxes array to JSON string if present
|
||||||
if (settings.searchBoxes !== undefined) {
|
if (settings.searchBoxes !== undefined) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(converted as any).searchBoxes = Array.isArray(settings.searchBoxes)
|
(converted as any).searchBoxes = Array.isArray(settings.searchBoxes)
|
||||||
? JSON.stringify(settings.searchBoxes)
|
? JSON.stringify(settings.searchBoxes)
|
||||||
: String(settings.searchBoxes);
|
: String(settings.searchBoxes);
|
||||||
@@ -692,6 +696,7 @@ export const PlatformServiceMixin = {
|
|||||||
typeof method.value === "string";
|
typeof method.value === "string";
|
||||||
|
|
||||||
if (!isValid && method !== undefined) {
|
if (!isValid && method !== undefined) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.warn(
|
console.warn(
|
||||||
"[ContactNormalization] Invalid contact method:",
|
"[ContactNormalization] Invalid contact method:",
|
||||||
method,
|
method,
|
||||||
|
|||||||
@@ -61,7 +61,8 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<!-- Currently disabled because it doesn't work, even on Chrome. If restored, make sure it works or doesn't show on mobile/electron. -->
|
<!-- Currently disabled because it doesn't work, even on Chrome.
|
||||||
|
If restored, make sure it works or doesn't show on mobile/electron. -->
|
||||||
<section
|
<section
|
||||||
v-if="false"
|
v-if="false"
|
||||||
id="sectionNotifications"
|
id="sectionNotifications"
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div id="ViewBreadcrumb" class="mb-8">
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
<h1 id="ViewHeading" class="text-lg text-center font-light relative px-7">
|
<h1 id="ViewHeading" class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Go to 'contacts' instead of just 'back' because they could get here from an edit page (and going back there is annoying). -->
|
<!-- Go to 'contacts' instead of just 'back' because they could get here from an edit page
|
||||||
|
(and going back there is annoying). -->
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'contacts' }"
|
:to="{ name: 'contacts' }"
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
|||||||
@@ -234,7 +234,6 @@ Raymer * @version 1.0.0 */
|
|||||||
:last-viewed-claim-id="feedLastViewedClaimId"
|
:last-viewed-claim-id="feedLastViewedClaimId"
|
||||||
:is-registered="isRegistered"
|
:is-registered="isRegistered"
|
||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:on-image-cache="cacheImageData"
|
|
||||||
@load-claim="onClickLoadClaim"
|
@load-claim="onClickLoadClaim"
|
||||||
@view-image="openImageViewer"
|
@view-image="openImageViewer"
|
||||||
/>
|
/>
|
||||||
@@ -255,11 +254,7 @@ Raymer * @version 1.0.0 */
|
|||||||
|
|
||||||
<ChoiceButtonDialog ref="choiceButtonDialog" />
|
<ChoiceButtonDialog ref="choiceButtonDialog" />
|
||||||
|
|
||||||
<ImageViewer
|
<ImageViewer v-model:is-open="isImageViewerOpen" :image-url="selectedImage" />
|
||||||
v-model:is-open="isImageViewerOpen"
|
|
||||||
:image-url="selectedImage"
|
|
||||||
:image-data="selectedImageData"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -434,9 +429,7 @@ export default class HomeView extends Vue {
|
|||||||
showShortcutBvc = false;
|
showShortcutBvc = false;
|
||||||
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
|
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
|
||||||
selectedImage = "";
|
selectedImage = "";
|
||||||
selectedImageData: Blob | null = null;
|
|
||||||
isImageViewerOpen = false;
|
isImageViewerOpen = false;
|
||||||
imageCache: Map<string, Blob | null> = new Map();
|
|
||||||
showProjectsDialog = false;
|
showProjectsDialog = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1614,23 +1607,6 @@ export default class HomeView extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Caches image data for sharing
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
* Called by ActivityListItem component function prop
|
|
||||||
* @param imageUrl URL of image to cache
|
|
||||||
*/
|
|
||||||
async cacheImageData(imageUrl: string) {
|
|
||||||
try {
|
|
||||||
// For images that might fail CORS, just store the URL
|
|
||||||
// The Web Share API will handle sharing the URL appropriately
|
|
||||||
this.imageCache.set(imageUrl, null);
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn("Failed to cache image:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens image viewer dialog
|
* Opens image viewer dialog
|
||||||
*
|
*
|
||||||
@@ -1639,7 +1615,6 @@ export default class HomeView extends Vue {
|
|||||||
* @param imageUrl URL of image to display
|
* @param imageUrl URL of image to display
|
||||||
*/
|
*/
|
||||||
async openImageViewer(imageUrl: string) {
|
async openImageViewer(imageUrl: string) {
|
||||||
this.selectedImageData = this.imageCache.get(imageUrl) ?? null;
|
|
||||||
this.selectedImage = imageUrl;
|
this.selectedImage = imageUrl;
|
||||||
this.isImageViewerOpen = true;
|
this.isImageViewerOpen = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,17 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div v-if="isNotProdServer">
|
||||||
|
<h2 class="text-xl font-bold mb-4">User Registration</h2>
|
||||||
|
<button :class="primaryButtonClasses" @click="registerMe()">
|
||||||
|
Register Yourself
|
||||||
|
</button>
|
||||||
|
<button :class="primaryButtonClasses" @click="becomeUser0()">
|
||||||
|
Become User 0 (who can register others)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
<h2 class="text-xl font-bold mb-4">Notiwind Alerts</h2>
|
<h2 class="text-xl font-bold mb-4">Notiwind Alerts</h2>
|
||||||
|
|
||||||
<!-- Notification test buttons using computed configuration -->
|
<!-- Notification test buttons using computed configuration -->
|
||||||
@@ -99,7 +109,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
Register Passkey
|
Register Passkey
|
||||||
<button :class="primaryButtonClasses" @click="register()">
|
<button :class="primaryButtonClasses" @click="registerPasskey()">
|
||||||
Simplewebauthn
|
Simplewebauthn
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -235,6 +245,7 @@ import {
|
|||||||
registerAndSavePasskey,
|
registerAndSavePasskey,
|
||||||
SHARED_PHOTO_BASE64_KEY,
|
SHARED_PHOTO_BASE64_KEY,
|
||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
|
import { testBecomeUser0, testServerRegisterUser } from "@/test";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { Account } from "../db/tables/accounts";
|
import { Account } from "../db/tables/accounts";
|
||||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
@@ -300,6 +311,7 @@ export default class Help extends Vue {
|
|||||||
// for passkeys
|
// for passkeys
|
||||||
credIdHex?: string;
|
credIdHex?: string;
|
||||||
activeDid?: string;
|
activeDid?: string;
|
||||||
|
apiServer?: string;
|
||||||
jwt?: string;
|
jwt?: string;
|
||||||
peerSetup?: PeerSetup;
|
peerSetup?: PeerSetup;
|
||||||
userName?: string;
|
userName?: string;
|
||||||
@@ -521,17 +533,6 @@ export default class Help extends Vue {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to trigger notification test
|
|
||||||
* Centralizes notification testing logic
|
|
||||||
*/
|
|
||||||
triggerTestNotification(config: {
|
|
||||||
notification: NotificationIface;
|
|
||||||
timeout?: number;
|
|
||||||
}) {
|
|
||||||
this.$notify(config.notification, config.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component initialization
|
* Component initialization
|
||||||
*
|
*
|
||||||
@@ -541,6 +542,7 @@ export default class Help extends Vue {
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
|
this.apiServer = settings.apiServer || "";
|
||||||
this.userName = settings.firstName;
|
this.userName = settings.firstName;
|
||||||
|
|
||||||
const account = await retrieveAccountMetadata(this.activeDid);
|
const account = await retrieveAccountMetadata(this.activeDid);
|
||||||
@@ -553,6 +555,43 @@ export default class Help extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if running on production server
|
||||||
|
*
|
||||||
|
* @returns True if not on production server (enables test utilities)
|
||||||
|
*/
|
||||||
|
public isNotProdServer() {
|
||||||
|
return this.apiServer !== AppString.PROD_ENDORSER_API_SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerMe() {
|
||||||
|
const response = await testServerRegisterUser();
|
||||||
|
if (response.status === 201) {
|
||||||
|
alert("Registration successful.");
|
||||||
|
this.$router.push({ name: "home" }); // because this page checks for registered status and sets things if it detects a change
|
||||||
|
} else {
|
||||||
|
logger.error("Registration failure response:", response);
|
||||||
|
alert("Registration failed: " + (response.data.error || response.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async becomeUser0() {
|
||||||
|
await testBecomeUser0();
|
||||||
|
alert("You are now User 0.");
|
||||||
|
this.$router.push({ name: "home" }); // because this page checks for registered status and sets things if it detects a change
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to trigger notification test
|
||||||
|
* Centralizes notification testing logic
|
||||||
|
*/
|
||||||
|
triggerTestNotification(config: {
|
||||||
|
notification: NotificationIface;
|
||||||
|
timeout?: number;
|
||||||
|
}) {
|
||||||
|
this.$notify(config.notification, config.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles file upload for image sharing tests
|
* Handles file upload for image sharing tests
|
||||||
*
|
*
|
||||||
@@ -609,7 +648,7 @@ export default class Help extends Vue {
|
|||||||
* Includes validation and user confirmation workflow
|
* Includes validation and user confirmation workflow
|
||||||
* Uses notification helpers for consistent messaging
|
* Uses notification helpers for consistent messaging
|
||||||
*/
|
*/
|
||||||
public async register() {
|
public async registerPasskey() {
|
||||||
const DEFAULT_USERNAME = AppString.APP_NAME + " Tester";
|
const DEFAULT_USERNAME = AppString.APP_NAME + " Tester";
|
||||||
if (!this.userName) {
|
if (!this.userName) {
|
||||||
const modalConfig = createPasskeyNameModal(
|
const modalConfig = createPasskeyNameModal(
|
||||||
|
|||||||
Reference in New Issue
Block a user