Compare commits
7 Commits
ui-fixes-2
...
master-set
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3881144c62 | ||
| 8609f8458d | |||
| 8f5c34bc5f | |||
| b0d61b95ea | |||
| af7bd236a3 | |||
| d719338bcc | |||
| 6ddf2d1012 |
56
BUILDING.md
56
BUILDING.md
@@ -9,19 +9,6 @@ For a quick dev environment setup, use [pkgx](https://pkgx.dev).
|
|||||||
- Node.js (LTS version recommended)
|
- Node.js (LTS version recommended)
|
||||||
- npm (comes with Node.js)
|
- npm (comes with Node.js)
|
||||||
- Git
|
- Git
|
||||||
- For Android builds: Android Studio with SDK installed
|
|
||||||
- For iOS builds: macOS with Xcode and ruby gems & bundle
|
|
||||||
- `pkgx +rubygems.org sh`
|
|
||||||
|
|
||||||
- ... and you may have to fix these, especially with pkgx
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gem_path=$(which gem)
|
|
||||||
shortened_path="${gem_path:h:h}"
|
|
||||||
export GEM_HOME=$shortened_path
|
|
||||||
export GEM_PATH=$shortened_path
|
|
||||||
```
|
|
||||||
|
|
||||||
- For desktop builds: Additional build tools based on your OS
|
- For desktop builds: Additional build tools based on your OS
|
||||||
|
|
||||||
## Forks
|
## Forks
|
||||||
@@ -326,6 +313,32 @@ npm run build:electron-prod && npm run electron:start
|
|||||||
|
|
||||||
Prerequisites: macOS with Xcode installed
|
Prerequisites: macOS with Xcode installed
|
||||||
|
|
||||||
|
#### First-time iOS Configuration
|
||||||
|
|
||||||
|
- Generate certificates inside XCode.
|
||||||
|
|
||||||
|
- Right-click on App and under Signing & Capabilities set the Team.
|
||||||
|
|
||||||
|
#### Each Release
|
||||||
|
|
||||||
|
0. First time (or if XCode dependencies change):
|
||||||
|
|
||||||
|
- `pkgx +rubygems.org sh`
|
||||||
|
|
||||||
|
- ... and you may have to fix these, especially with pkgx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gem_path=$(which gem)
|
||||||
|
shortened_path="${gem_path:h:h}"
|
||||||
|
export GEM_HOME=$shortened_path
|
||||||
|
export GEM_PATH=$shortened_path
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ios/App
|
||||||
|
pod install
|
||||||
|
```
|
||||||
|
|
||||||
1. Build the web assets:
|
1. Build the web assets:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -334,6 +347,7 @@ Prerequisites: macOS with Xcode installed
|
|||||||
npm run build:capacitor
|
npm run build:capacitor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
2. Update iOS project with latest build:
|
2. Update iOS project with latest build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -357,10 +371,10 @@ Prerequisites: macOS with Xcode installed
|
|||||||
|
|
||||||
```
|
```
|
||||||
cd ios/App
|
cd ios/App
|
||||||
xcrun agvtool new-version 21
|
xcrun agvtool new-version 25
|
||||||
# Unfortunately this edits Info.plist directly.
|
# Unfortunately this edits Info.plist directly.
|
||||||
#xcrun agvtool new-marketing-version 0.4.5
|
#xcrun agvtool new-marketing-version 0.4.5
|
||||||
cat App.xcodeproj/project.pbxproj | sed "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 0.4.7;/g" > temp
|
cat App.xcodeproj/project.pbxproj | sed "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 0.5.0;/g" > temp
|
||||||
mv temp App.xcodeproj/project.pbxproj
|
mv temp App.xcodeproj/project.pbxproj
|
||||||
cd -
|
cd -
|
||||||
```
|
```
|
||||||
@@ -377,7 +391,7 @@ Prerequisites: macOS with Xcode installed
|
|||||||
|
|
||||||
7. Release
|
7. Release
|
||||||
|
|
||||||
* Under "General" we want to rename a bunch of things to "Time Safari"
|
* Someday: Under "General" we want to rename a bunch of things to "Time Safari"
|
||||||
* Choose Product -> Destination -> Any iOS Device
|
* Choose Product -> Destination -> Any iOS Device
|
||||||
* Choose Product -> Archive
|
* Choose Product -> Archive
|
||||||
* This will trigger a build and take time, needing user's "login" keychain password (user's login password), repeatedly.
|
* This will trigger a build and take time, needing user's "login" keychain password (user's login password), repeatedly.
|
||||||
@@ -389,15 +403,9 @@ Prerequisites: macOS with Xcode installed
|
|||||||
* You'll probably have to "Manage" something about encryption, disallowed in France.
|
* You'll probably have to "Manage" something about encryption, disallowed in France.
|
||||||
* Then "Save" and "Add to Review" and "Resubmit to App Review".
|
* Then "Save" and "Add to Review" and "Resubmit to App Review".
|
||||||
|
|
||||||
#### First-time iOS Configuration
|
|
||||||
|
|
||||||
- Generate certificates inside XCode.
|
|
||||||
|
|
||||||
- Right-click on App and under Signing & Capabilities set the Team.
|
|
||||||
|
|
||||||
### Android Build
|
### Android Build
|
||||||
|
|
||||||
Prerequisites: Android Studio with SDK installed
|
Prerequisites: Android Studio with Java SDK installed
|
||||||
|
|
||||||
1. Build the web assets:
|
1. Build the web assets:
|
||||||
|
|
||||||
@@ -452,7 +460,9 @@ Prerequisites: Android Studio with SDK installed
|
|||||||
* Then `bundleRelease`:
|
* Then `bundleRelease`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cd android
|
||||||
./gradlew bundleRelease -Dlint.baselines.continue=true
|
./gradlew bundleRelease -Dlint.baselines.continue=true
|
||||||
|
cd -
|
||||||
```
|
```
|
||||||
|
|
||||||
... and find your `aab` file at app/build/outputs/bundle/release
|
... and find your `aab` file at app/build/outputs/bundle/release
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ android {
|
|||||||
applicationId "app.timesafari.app"
|
applicationId "app.timesafari.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 23
|
versionCode 25
|
||||||
versionName "0.4.8"
|
versionName "0.5.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios": {
|
||||||
"contentInset": "always",
|
"contentInset": "never",
|
||||||
"allowsLinkPreview": true,
|
"allowsLinkPreview": true,
|
||||||
"scrollEnabled": true,
|
"scrollEnabled": true,
|
||||||
"limitsNavigationsToAppBoundDomains": true,
|
"limitsNavigationsToAppBoundDomains": true,
|
||||||
|
|||||||
@@ -403,7 +403,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 23;
|
CURRENT_PROJECT_VERSION = 25;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
@@ -413,7 +413,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.4.8;
|
MARKETING_VERSION = 0.5.0;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -430,7 +430,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 23;
|
CURRENT_PROJECT_VERSION = 25;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.4.8;
|
MARKETING_VERSION = 0.5.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
|
|||||||
@@ -49,7 +49,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)" data-testid="circle-info-link">
|
<a
|
||||||
|
class="cursor-pointer"
|
||||||
|
data-testid="circle-info-link"
|
||||||
|
@click="$emit('loadClaim', record.jwtId)"
|
||||||
|
>
|
||||||
<font-awesome icon="circle-info" class="fa-fw text-slate-500" />
|
<font-awesome icon="circle-info" class="fa-fw text-slate-500" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,10 +48,7 @@
|
|||||||
<span>
|
<span>
|
||||||
{{ didInfo(visDid) }}
|
{{ didInfo(visDid) }}
|
||||||
<span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)">
|
<span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)">
|
||||||
<a
|
<a :href="`/did/${visDid}`" class="text-blue-500">
|
||||||
:href="`/did/${visDid}`"
|
|
||||||
class="text-blue-500"
|
|
||||||
>
|
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="arrow-up-right-from-square"
|
icon="arrow-up-right-from-square"
|
||||||
class="fa-fw"
|
class="fa-fw"
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
The feed underneath this pop-up shows the latest contributions,
|
The feed underneath this pop-up shows the latest contributions, some from
|
||||||
some from people and some from projects.
|
people and some from projects.
|
||||||
|
|
||||||
<p v-if="isRegistered" class="mt-4">
|
<p v-if="isRegistered" class="mt-4">
|
||||||
You can now log things that you've seen:
|
You can now log things that you've seen:
|
||||||
@@ -29,8 +29,7 @@
|
|||||||
button to express your appreciation for... whatever.
|
button to express your appreciation for... whatever.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-4">
|
<p class="mt-4">
|
||||||
Once someone registers you, you can log your
|
Once someone registers you, you can log your appreciation, too.
|
||||||
appreciation, too.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="mt-4">
|
<p class="mt-4">
|
||||||
|
|||||||
@@ -8,11 +8,7 @@
|
|||||||
>
|
>
|
||||||
<div class="h-full w-full object-contain" v-html="generateIcon()" />
|
<div class="h-full w-full object-contain" v-html="generateIcon()" />
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div v-else class="h-full w-full object-contain" v-html="generateIcon()" />
|
||||||
v-else
|
|
||||||
class="h-full w-full object-contain"
|
|
||||||
v-html="generateIcon()"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toSvg } from "jdenticon";
|
import { toSvg } from "jdenticon";
|
||||||
|
|||||||
@@ -33,18 +33,18 @@ export const APP_SERVER =
|
|||||||
|
|
||||||
export const DEFAULT_ENDORSER_API_SERVER =
|
export const DEFAULT_ENDORSER_API_SERVER =
|
||||||
import.meta.env.VITE_DEFAULT_ENDORSER_API_SERVER ||
|
import.meta.env.VITE_DEFAULT_ENDORSER_API_SERVER ||
|
||||||
AppString.TEST_ENDORSER_API_SERVER;
|
AppString.PROD_ENDORSER_API_SERVER;
|
||||||
|
|
||||||
export const DEFAULT_IMAGE_API_SERVER =
|
export const DEFAULT_IMAGE_API_SERVER =
|
||||||
import.meta.env.VITE_DEFAULT_IMAGE_API_SERVER ||
|
import.meta.env.VITE_DEFAULT_IMAGE_API_SERVER ||
|
||||||
AppString.TEST_IMAGE_API_SERVER;
|
AppString.PROD_IMAGE_API_SERVER;
|
||||||
|
|
||||||
export const DEFAULT_PARTNER_API_SERVER =
|
export const DEFAULT_PARTNER_API_SERVER =
|
||||||
import.meta.env.VITE_DEFAULT_PARTNER_API_SERVER ||
|
import.meta.env.VITE_DEFAULT_PARTNER_API_SERVER ||
|
||||||
AppString.TEST_PARTNER_API_SERVER;
|
AppString.PROD_PARTNER_API_SERVER;
|
||||||
|
|
||||||
export const DEFAULT_PUSH_SERVER =
|
export const DEFAULT_PUSH_SERVER =
|
||||||
import.meta.env.VITE_DEFAULT_PUSH_SERVER || "https://timesafari.app";
|
import.meta.env.VITE_DEFAULT_PUSH_SERVER || AppString.PROD_PUSH_SERVER;
|
||||||
|
|
||||||
export const IMAGE_TYPE_PROFILE = "profile";
|
export const IMAGE_TYPE_PROFILE = "profile";
|
||||||
|
|
||||||
|
|||||||
@@ -55,20 +55,7 @@ export async function updateAccountSettings(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updateResult = await platform.dbExec(updateSql, updateParams);
|
const updateResult = await platform.dbExec(updateSql, updateParams);
|
||||||
|
return updateResult.changes === 1;
|
||||||
// If no record was updated, insert a new one
|
|
||||||
if (updateResult.changes === 1) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
const columns = Object.keys(settingsChanges);
|
|
||||||
const values = Object.values(settingsChanges);
|
|
||||||
const placeholders = values.map(() => "?").join(", ");
|
|
||||||
|
|
||||||
const insertSql = `INSERT INTO settings (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
||||||
const result = await platform.dbExec(insertSql, values);
|
|
||||||
|
|
||||||
return result.changes === 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: Settings = {
|
const DEFAULT_SETTINGS: Settings = {
|
||||||
@@ -130,34 +117,14 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
|
|||||||
return defaultSettings;
|
return defaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map and filter settings
|
// Map settings
|
||||||
const overrideSettings = mapColumnsToValues(
|
const overrideSettings = mapColumnsToValues(
|
||||||
result.columns,
|
result.columns,
|
||||||
result.values,
|
result.values,
|
||||||
)[0] as Settings;
|
)[0] as Settings;
|
||||||
const overrideSettingsFiltered = Object.fromEntries(
|
|
||||||
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Merge settings
|
// Use the new mergeSettings function for consistency
|
||||||
const settings = { ...defaultSettings, ...overrideSettingsFiltered };
|
return mergeSettings(defaultSettings, overrideSettings);
|
||||||
|
|
||||||
// Handle searchBoxes parsing
|
|
||||||
if (settings.searchBoxes) {
|
|
||||||
try {
|
|
||||||
// @ts-expect-error - the searchBoxes field is a string in the DB
|
|
||||||
settings.searchBoxes = JSON.parse(settings.searchBoxes);
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleAndDb(
|
|
||||||
`[databaseUtil] Failed to parse searchBoxes for ${defaultSettings.activeDid}: ${error}`,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
// Reset to empty array on parse failure
|
|
||||||
settings.searchBoxes = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleAndDb(
|
logConsoleAndDb(
|
||||||
`[databaseUtil] Failed to retrieve account settings for ${defaultSettings.activeDid}: ${error}`,
|
`[databaseUtil] Failed to retrieve account settings for ${defaultSettings.activeDid}: ${error}`,
|
||||||
@@ -325,3 +292,128 @@ export function mapColumnsToValues(
|
|||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves settings for a specific account by DID
|
||||||
|
* @param accountDid - The DID of the account to retrieve settings for
|
||||||
|
* @returns Promise<Settings> Combined settings for the specified account
|
||||||
|
*/
|
||||||
|
export async function getSettingsForAccount(accountDid: string): Promise<Settings> {
|
||||||
|
try {
|
||||||
|
// Get default settings first
|
||||||
|
const defaultSettings = await retrieveSettingsForDefaultAccount();
|
||||||
|
|
||||||
|
// Get account-specific settings
|
||||||
|
const platform = PlatformServiceFactory.getInstance();
|
||||||
|
const result = await platform.dbQuery(
|
||||||
|
"SELECT * FROM settings WHERE accountDid = ?",
|
||||||
|
[accountDid],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result?.values?.length) {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[databaseUtil] No account-specific settings found for ${accountDid}`,
|
||||||
|
);
|
||||||
|
return defaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map and merge settings
|
||||||
|
const overrideSettings = mapColumnsToValues(
|
||||||
|
result.columns,
|
||||||
|
result.values,
|
||||||
|
)[0] as Settings;
|
||||||
|
|
||||||
|
return mergeSettings(defaultSettings, overrideSettings);
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[databaseUtil] Failed to retrieve settings for account ${accountDid}: ${error}`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// Return default settings on error
|
||||||
|
return await retrieveSettingsForDefaultAccount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves only account-specific settings for a given DID (without merging with defaults)
|
||||||
|
* @param accountDid - The DID of the account to retrieve settings for
|
||||||
|
* @returns Promise<Settings> Account-specific settings only
|
||||||
|
*/
|
||||||
|
export async function getAccountSpecificSettings(accountDid: string): Promise<Settings> {
|
||||||
|
try {
|
||||||
|
const platform = PlatformServiceFactory.getInstance();
|
||||||
|
const result = await platform.dbQuery(
|
||||||
|
"SELECT * FROM settings WHERE accountDid = ?",
|
||||||
|
[accountDid],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result?.values?.length) {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[databaseUtil] No account-specific settings found for ${accountDid}`,
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map settings
|
||||||
|
const settings = mapColumnsToValues(
|
||||||
|
result.columns,
|
||||||
|
result.values,
|
||||||
|
)[0] as Settings;
|
||||||
|
|
||||||
|
// Handle searchBoxes parsing
|
||||||
|
if (settings.searchBoxes) {
|
||||||
|
try {
|
||||||
|
// @ts-expect-error - the searchBoxes field is a string in the DB
|
||||||
|
settings.searchBoxes = JSON.parse(settings.searchBoxes);
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[databaseUtil] Failed to parse searchBoxes for ${accountDid}: ${error}`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// Reset to empty array on parse failure
|
||||||
|
settings.searchBoxes = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[databaseUtil] Failed to retrieve account-specific settings for ${accountDid}: ${error}`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges default settings with account-specific settings using consistent logic
|
||||||
|
* @param defaultSettings - The default/master settings
|
||||||
|
* @param accountSettings - The account-specific settings to merge
|
||||||
|
* @returns Settings - Merged settings with account-specific overrides
|
||||||
|
*/
|
||||||
|
export function mergeSettings(defaultSettings: Settings, accountSettings: Settings): Settings {
|
||||||
|
// Filter out null values from account settings
|
||||||
|
const accountSettingsFiltered = Object.fromEntries(
|
||||||
|
Object.entries(accountSettings).filter(([_, v]) => v !== null),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perform shallow merge (account settings override defaults)
|
||||||
|
const mergedSettings = { ...defaultSettings, ...accountSettingsFiltered };
|
||||||
|
|
||||||
|
// Handle searchBoxes parsing if present
|
||||||
|
if (mergedSettings.searchBoxes) {
|
||||||
|
try {
|
||||||
|
// @ts-expect-error - the searchBoxes field is a string in the DB
|
||||||
|
mergedSettings.searchBoxes = JSON.parse(mergedSettings.searchBoxes);
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[databaseUtil] Failed to parse searchBoxes during merge: ${error}`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// Reset to empty array on parse failure
|
||||||
|
mergedSettings.searchBoxes = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedSettings;
|
||||||
|
}
|
||||||
|
|||||||
@@ -265,6 +265,43 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves settings for a specific account by DID
|
||||||
|
* @param accountDid - The DID of the account to retrieve settings for
|
||||||
|
* @returns Promise<Settings> Combined settings for the specified account
|
||||||
|
*/
|
||||||
|
export async function getSettingsForAccount(accountDid: string): Promise<Settings> {
|
||||||
|
const defaultSettings = await retrieveSettingsForDefaultAccount();
|
||||||
|
const overrideSettings =
|
||||||
|
(await db.settings
|
||||||
|
.where("accountDid")
|
||||||
|
.equals(accountDid)
|
||||||
|
.first()) || {};
|
||||||
|
return R.mergeDeepRight(defaultSettings, overrideSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves only account-specific settings for a given DID (without merging with defaults)
|
||||||
|
* @param accountDid - The DID of the account to retrieve settings for
|
||||||
|
* @returns Promise<Settings> Account-specific settings only
|
||||||
|
*/
|
||||||
|
export async function getAccountSpecificSettings(accountDid: string): Promise<Settings> {
|
||||||
|
return (await db.settings
|
||||||
|
.where("accountDid")
|
||||||
|
.equals(accountDid)
|
||||||
|
.first()) || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges default settings with account-specific settings using consistent logic
|
||||||
|
* @param defaultSettings - The default/master settings
|
||||||
|
* @param accountSettings - The account-specific settings to merge
|
||||||
|
* @returns Settings - Merged settings with account-specific overrides
|
||||||
|
*/
|
||||||
|
export function mergeSettings(defaultSettings: Settings, accountSettings: Settings): Settings {
|
||||||
|
return R.mergeDeepRight(defaultSettings, accountSettings);
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateAccountSettings(
|
export async function updateAccountSettings(
|
||||||
accountDid: string,
|
accountDid: string,
|
||||||
settingsChanges: Settings,
|
settingsChanges: Settings,
|
||||||
|
|||||||
@@ -626,7 +626,9 @@ export const retrieveFullyDecryptedAccount = async (
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const retrieveAllAccountsMetadata = async (): Promise<AccountEncrypted[]> => {
|
export const retrieveAllAccountsMetadata = async (): Promise<
|
||||||
|
AccountEncrypted[]
|
||||||
|
> => {
|
||||||
const platformService = PlatformServiceFactory.getInstance();
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
const dbAccounts = await platformService.dbQuery(`SELECT * FROM accounts`);
|
const dbAccounts = await platformService.dbQuery(`SELECT * FROM accounts`);
|
||||||
const accounts = databaseUtil.mapQueryResultToValues(dbAccounts) as Account[];
|
const accounts = databaseUtil.mapQueryResultToValues(dbAccounts) as Account[];
|
||||||
@@ -643,8 +645,12 @@ export const retrieveAllAccountsMetadata = async (): Promise<AccountEncrypted[]>
|
|||||||
// This is not accurate because they can't be decrypted, but we're removing Dexie anyway.
|
// This is not accurate because they can't be decrypted, but we're removing Dexie anyway.
|
||||||
const identityStr = JSON.stringify(identity);
|
const identityStr = JSON.stringify(identity);
|
||||||
const encryptedAccount = {
|
const encryptedAccount = {
|
||||||
identityEncrBase64: sha256(new TextEncoder().encode(identityStr)).toString(),
|
identityEncrBase64: sha256(
|
||||||
mnemonicEncrBase64: sha256(new TextEncoder().encode(account.mnemonic)).toString(),
|
new TextEncoder().encode(identityStr),
|
||||||
|
).toString(),
|
||||||
|
mnemonicEncrBase64: sha256(
|
||||||
|
new TextEncoder().encode(account.mnemonic),
|
||||||
|
).toString(),
|
||||||
...metadata,
|
...metadata,
|
||||||
};
|
};
|
||||||
return encryptedAccount as AccountEncrypted;
|
return encryptedAccount as AccountEncrypted;
|
||||||
|
|||||||
@@ -211,7 +211,7 @@
|
|||||||
@click="handleQRCodeClick"
|
@click="handleQRCodeClick"
|
||||||
>
|
>
|
||||||
Share Your Info
|
Share Your Info
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@@ -1015,7 +1015,6 @@ import {
|
|||||||
retrieveSettingsForActiveAccount,
|
retrieveSettingsForActiveAccount,
|
||||||
updateAccountSettings,
|
updateAccountSettings,
|
||||||
} from "../db/index";
|
} from "../db/index";
|
||||||
import { Account } from "../db/tables/accounts";
|
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import {
|
import {
|
||||||
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
|
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
|
||||||
@@ -1040,7 +1039,6 @@ import {
|
|||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
import { UserProfile } from "@/libs/partnerServer";
|
import { UserProfile } from "@/libs/partnerServer";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
|
||||||
|
|
||||||
const inputImportFileNameRef = ref<Blob>();
|
const inputImportFileNameRef = ref<Blob>();
|
||||||
|
|
||||||
@@ -1174,8 +1172,6 @@ export default class AccountViewView extends Vue {
|
|||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
this.loadingProfile = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1198,6 +1194,8 @@ export default class AccountViewView extends Vue {
|
|||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
this.loadingProfile = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1240,7 +1238,6 @@ export default class AccountViewView extends Vue {
|
|||||||
*/
|
*/
|
||||||
async initializeState() {
|
async initializeState() {
|
||||||
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||||
console.log("settings", settings);
|
|
||||||
if (USE_DEXIE_DB) {
|
if (USE_DEXIE_DB) {
|
||||||
await db.open();
|
await db.open();
|
||||||
settings = await retrieveSettingsForActiveAccount();
|
settings = await retrieveSettingsForActiveAccount();
|
||||||
|
|||||||
@@ -292,10 +292,7 @@
|
|||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
{{ didInfo(confirmerId) }}
|
{{ didInfo(confirmerId) }}
|
||||||
<span v-if="!serverUtil.isEmptyOrHiddenDid(confirmerId)">
|
<span v-if="!serverUtil.isEmptyOrHiddenDid(confirmerId)">
|
||||||
<a
|
<a :href="`/did/${confirmerId}`" class="text-blue-500">
|
||||||
:href="`/did/${confirmerId}`"
|
|
||||||
class="text-blue-500"
|
|
||||||
>
|
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="arrow-up-right-from-square"
|
icon="arrow-up-right-from-square"
|
||||||
class="fa-fw"
|
class="fa-fw"
|
||||||
@@ -332,10 +329,7 @@
|
|||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
{{ didInfo(confsVisibleTo) }}
|
{{ didInfo(confsVisibleTo) }}
|
||||||
<span v-if="!serverUtil.isEmptyOrHiddenDid(confsVisibleTo)">
|
<span v-if="!serverUtil.isEmptyOrHiddenDid(confsVisibleTo)">
|
||||||
<a
|
<a :href="`/did/${confsVisibleTo}`" class="text-blue-500">
|
||||||
:href="`/did/${confsVisibleTo}`"
|
|
||||||
class="text-blue-500"
|
|
||||||
>
|
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="arrow-up-right-from-square"
|
icon="arrow-up-right-from-square"
|
||||||
class="fa-fw"
|
class="fa-fw"
|
||||||
@@ -449,10 +443,7 @@
|
|||||||
<span>
|
<span>
|
||||||
{{ didInfo(visDid) }}
|
{{ didInfo(visDid) }}
|
||||||
<span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)">
|
<span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)">
|
||||||
<a
|
<a :href="`/did/${visDid}`" class="text-blue-500">
|
||||||
:href="`/did/${visDid}`"
|
|
||||||
class="text-blue-500"
|
|
||||||
>
|
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="arrow-up-right-from-square"
|
icon="arrow-up-right-from-square"
|
||||||
class="fa-fw"
|
class="fa-fw"
|
||||||
|
|||||||
@@ -825,10 +825,7 @@ export default class GiftedDetails extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!result.success) {
|
||||||
result.type === "error" ||
|
|
||||||
this.isGiveCreationError(result.response)
|
|
||||||
) {
|
|
||||||
const errorMessage = this.getGiveCreationErrorMessage(result);
|
const errorMessage = this.getGiveCreationErrorMessage(result);
|
||||||
logger.error("Error with give creation result:", result);
|
logger.error("Error with give creation result:", result);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -902,15 +899,6 @@ export default class GiftedDetails extends Vue {
|
|||||||
|
|
||||||
// Helper functions for readability
|
// Helper functions for readability
|
||||||
|
|
||||||
/**
|
|
||||||
* @param result response "data" from the server
|
|
||||||
* @returns true if the result indicates an error
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
isGiveCreationError(result: any) {
|
|
||||||
return result.status !== 201 || result.data?.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
|
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
|
||||||
* @returns best guess at an error message
|
* @returns best guess at an error message
|
||||||
|
|||||||
@@ -81,7 +81,11 @@ import {
|
|||||||
} from "../libs/crypto";
|
} from "../libs/crypto";
|
||||||
import { accountsDBPromise, db } from "../db/index";
|
import { accountsDBPromise, db } from "../db/index";
|
||||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
||||||
import { retrieveAllAccountsMetadata, retrieveFullyDecryptedAccount, saveNewIdentity } from "../libs/util";
|
import {
|
||||||
|
retrieveAllAccountsMetadata,
|
||||||
|
retrieveFullyDecryptedAccount,
|
||||||
|
saveNewIdentity,
|
||||||
|
} from "../libs/util";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
||||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||||
@@ -100,13 +104,20 @@ export default class ImportAccountView extends Vue {
|
|||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
const accounts: AccountEncrypted[] = await retrieveAllAccountsMetadata();
|
const accounts: AccountEncrypted[] = await retrieveAllAccountsMetadata();
|
||||||
const decryptedAccounts: (Account | undefined)[] = await Promise.all(accounts.map(async (account) => {
|
const decryptedAccounts: (Account | undefined)[] = await Promise.all(
|
||||||
return retrieveFullyDecryptedAccount(account.did);
|
accounts.map(async (account) => {
|
||||||
}));
|
return retrieveFullyDecryptedAccount(account.did);
|
||||||
const filteredDecryptedAccounts: Account[] = decryptedAccounts.filter((account) => account !== undefined);
|
}),
|
||||||
|
);
|
||||||
|
const filteredDecryptedAccounts: Account[] = decryptedAccounts.filter(
|
||||||
|
(account) => account !== undefined,
|
||||||
|
);
|
||||||
|
|
||||||
// group by account.mnemonic
|
// group by account.mnemonic
|
||||||
const groupedAccounts: Record<string, Account[]> = R.groupBy((a) => a.mnemonic || "", filteredDecryptedAccounts) as Record<string, Account[]>;
|
const groupedAccounts: Record<string, Account[]> = R.groupBy(
|
||||||
|
(a) => a.mnemonic || "",
|
||||||
|
filteredDecryptedAccounts,
|
||||||
|
) as Record<string, Account[]>;
|
||||||
|
|
||||||
this.didArrays = groupedAccounts;
|
this.didArrays = groupedAccounts;
|
||||||
if (Object.keys(this.didArrays).length > 0) {
|
if (Object.keys(this.didArrays).length > 0) {
|
||||||
@@ -125,10 +136,13 @@ export default class ImportAccountView extends Vue {
|
|||||||
public async incrementDerivation() {
|
public async incrementDerivation() {
|
||||||
// find the maximum derivation path for the selected DIDs
|
// find the maximum derivation path for the selected DIDs
|
||||||
const selectedArray: Array<Account> =
|
const selectedArray: Array<Account> =
|
||||||
Object.values(this.didArrays).find((dids) => dids[0].did === this.selectedArrayFirstDid) ||
|
Object.values(this.didArrays).find(
|
||||||
[];
|
(dids) => dids[0].did === this.selectedArrayFirstDid,
|
||||||
|
) || [];
|
||||||
// extract the derivationPath array and sort it
|
// extract the derivationPath array and sort it
|
||||||
const derivationPaths = selectedArray.map((account) => account.derivationPath);
|
const derivationPaths = selectedArray.map(
|
||||||
|
(account) => account.derivationPath,
|
||||||
|
);
|
||||||
derivationPaths.sort((a, b) => {
|
derivationPaths.sort((a, b) => {
|
||||||
const aParts = a?.split("/");
|
const aParts = a?.split("/");
|
||||||
const aLast = aParts?.[aParts.length - 1];
|
const aLast = aParts?.[aParts.length - 1];
|
||||||
@@ -137,7 +151,9 @@ export default class ImportAccountView extends Vue {
|
|||||||
return parseInt(aLast || "0") - parseInt(bLast || "0");
|
return parseInt(aLast || "0") - parseInt(bLast || "0");
|
||||||
});
|
});
|
||||||
// we're sure there's at least one
|
// we're sure there's at least one
|
||||||
const maxDerivPath: string = derivationPaths[derivationPaths.length - 1] as string;
|
const maxDerivPath: string = derivationPaths[
|
||||||
|
derivationPaths.length - 1
|
||||||
|
] as string;
|
||||||
|
|
||||||
const newDerivPath = nextDerivationPath(maxDerivPath);
|
const newDerivPath = nextDerivationPath(maxDerivPath);
|
||||||
|
|
||||||
|
|||||||
@@ -54,10 +54,7 @@
|
|||||||
></font-awesome>
|
></font-awesome>
|
||||||
{{ issuerInfoObject?.displayName }}
|
{{ issuerInfoObject?.displayName }}
|
||||||
<span v-if="!serverUtil.isEmptyOrHiddenDid(issuer)">
|
<span v-if="!serverUtil.isEmptyOrHiddenDid(issuer)">
|
||||||
<a
|
<a :href="`/did/${issuer}`" class="text-blue-500">
|
||||||
:href="`/did/${issuer}`"
|
|
||||||
class="text-blue-500"
|
|
||||||
>
|
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="arrow-up-right-from-square"
|
icon="arrow-up-right-from-square"
|
||||||
class="fa-fw"
|
class="fa-fw"
|
||||||
|
|||||||
@@ -230,7 +230,9 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
suppressMilliseconds: true,
|
suppressMilliseconds: true,
|
||||||
}) || "";
|
}) || "";
|
||||||
|
|
||||||
this.allMyDids = (await retrieveAllAccountsMetadata()).map((account) => account.did);
|
this.allMyDids = (await retrieveAllAccountsMetadata()).map(
|
||||||
|
(account) => account.did,
|
||||||
|
);
|
||||||
if (USE_DEXIE_DB) {
|
if (USE_DEXIE_DB) {
|
||||||
const accountsDB = await accountsDBPromise;
|
const accountsDB = await accountsDBPromise;
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
|
|||||||
Reference in New Issue
Block a user