Compare commits

...

28 Commits

Author SHA1 Message Date
3118f71320 fix linting (whitespace only) 2025-06-18 21:44:11 -06:00
d12f23aa81 Merge pull request 'Make all external URLs go to the /deep-link/ endpoint to redirect to mobile vs web' (#139) from deep-link-redirect into master
Reviewed-on: #139
2025-06-18 23:33:12 -04:00
e9a8a3c1e7 add support for deep-link query parameters 2025-06-18 19:31:16 -06:00
1e0efe6011 lengthen the error timeout when the message may be complicated, eg. with details from the server 2025-06-18 18:32:55 -06:00
16557f1e4b update build instruction & package-lock.json 2025-06-18 17:32:41 -06:00
c4a54967bc fix linting 2025-06-18 16:33:55 -06:00
20ade415dc bump to version 0.5.8 build 34 2025-06-18 16:31:31 -06:00
6689520270 fix all copies for externally-shared links to redirected deep links 2025-06-18 15:53:16 -06:00
3fd6c2b80d add first cut at deep-link redirecting, with one example contact-import that works on mobile 2025-06-18 13:16:17 -06:00
a5c5c2b9dd bump to build 33 and version 0.5.7 2025-06-18 02:34:18 -06:00
cf33a39fbc fix the invite-one setup, fix adeep link, and tweak other verbiage & error info 2025-06-18 02:32:47 -06:00
8629cefa13 bump to build 32 & version 0.5.6 2025-06-17 05:25:45 -06:00
5e851e442f shrink the contents of the QR code so people can scan it 2025-06-16 15:38:11 -06:00
4a43bc9c6c bump build to 31 and version to 0.5.5 2025-06-16 07:38:16 -06:00
60de8cee62 reword some of the help-page introduction (no code changes) 2025-06-16 07:24:37 -06:00
Jose Olarte III
bb2a4ab76e URL scheme config for iOS
- Registers the timesafari:// URL scheme
- Sets the bundle URL name to app.timesafari
2025-06-16 16:09:08 +08:00
Matthew Raymer
048dded278 fix: resolve deep link route mismatch for project links
- Fix schema validation mismatch between "project-details" and "project"
- Update VALID_DEEP_LINK_ROUTES to include "project" instead of "project-details"
- Update deepLinkSchemas to use "project" route name
- Update documentation to reflect correct route name
- Resolves "Invalid route path: project" errors in deep link handling

The deep link timesafari://project/01JWH0YAB3MAGBD751VAJAXQ17 now works correctly
and routes to the ProjectViewView component as expected.

Fixes: Deep link validation errors for project routes
2025-06-16 05:48:13 +00:00
e240c2940a remove unused deep links and add another 2025-06-15 13:54:12 -06:00
54dca9e745 fix project deep-link (and reorder alphabetically) 2025-06-15 11:02:16 -06:00
9f0fed0a60 update ios check to work, and add links to app stores 2025-06-14 22:10:49 -06:00
0d152adbf2 remove the deep-link autoVerify because it caused a build failure 2025-06-14 22:06:12 -06:00
cead308800 incorporate one of the BUILDING steps directly into the file 2025-06-13 22:37:03 -06:00
676a301331 bump to build 30 version 0.5.4 2025-06-13 22:36:28 -06:00
d6db81cc36 fix some result types and refactor types themselves 2025-06-13 21:58:57 -06:00
Matthew Raymer
f2ddcd2541 feat: add conditional rendering for claim certificate link and update gitignore
- Add v-if directive to show claim certificate link only when veriClaim.id exists
- Update .gitignore to exclude android app resource directory
- Prevents broken links when claim data is not fully loaded
- Improves build process by ignoring generated Android resources

This change ensures the certificate link is only displayed when there's
valid claim data available, preventing navigation errors and improving
user experience. The gitignore update helps keep the repository clean
by excluding Android-specific generated files.
2025-06-14 03:31:12 +00:00
fb81f7b96e fix problems with :href links causing the app to reload for DB errors on mobile 2025-06-13 20:39:12 -06:00
a23416ead1 fix optional message at top to not overflow 2025-06-12 20:10:31 -06:00
530c7c1a13 fix problem with user-profile page, and bump to build 29 & version 0.5.3 2025-06-12 19:16:02 -06:00
44 changed files with 1233 additions and 693 deletions

1
.gitignore vendored
View File

@@ -55,3 +55,4 @@ build_logs/
icons
android/app/src/main/res/

View File

@@ -41,6 +41,7 @@ Install dependencies:
1. Run the production build:
```bash
rm -rf dist
npm run build:web
```
@@ -64,6 +65,8 @@ Install dependencies:
* Commit everything (since the commit hash is used the app).
* Run a build to make sure package-lock version is updated, linting works, etc: `npm install && npm run build`
* Put the commit hash in the changelog (which will help you remember to bump the version later).
* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 0.3.55 && git push origin 0.3.55`.
@@ -71,7 +74,7 @@ Install dependencies:
* For test, build the app (because test server is not yet set up to build):
```bash
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_DEFAULT_PUSH_SERVER=https://test.timesafari.app VITE_PASSKEYS_ENABLED=true npm run build
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_DEFAULT_PUSH_SERVER=https://test.timesafari.app VITE_PASSKEYS_ENABLED=true npm run build:web
```
... and transfer to the test server:
@@ -321,11 +324,11 @@ Prerequisites: macOS with Xcode installed
#### Each Release
0. First time (or if XCode dependencies change):
0. First time (or if dependencies change):
- `pkgx +rubygems.org sh`
- ... and you may have to fix these, especially with pkgx
- ... and you may have to fix these, especially with pkgx:
```bash
gem_path=$(which gem)
@@ -334,23 +337,12 @@ Prerequisites: macOS with Xcode installed
export GEM_PATH=$shortened_path
```
```bash
cd ios/App
pod install
```
1. Build the web assets:
1. Build the web assets & update ios:
```bash
rm -rf dist
npm run build:web
npm run build:capacitor
```
2. Update iOS project with latest build:
```bash
npx cap sync ios
```
@@ -367,15 +359,14 @@ Prerequisites: macOS with Xcode installed
npx capacitor-assets generate --ios
```
4. Bump the version to match Android:
4. Bump the version to match Android & package.json:
```
cd ios/App
xcrun agvtool new-version 25
xcrun agvtool new-version 34
# Unfortunately this edits Info.plist directly.
#xcrun agvtool new-marketing-version 0.4.5
cat App.xcodeproj/project.pbxproj | sed "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 0.5.1;/g" > temp
mv temp App.xcodeproj/project.pbxproj
cat App.xcodeproj/project.pbxproj | sed "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 0.5.8;/g" > temp && mv temp App.xcodeproj/project.pbxproj
cd -
```
@@ -427,7 +418,7 @@ Prerequisites: Android Studio with Java SDK installed
npx capacitor-assets generate --android
```
4. Bump version to match iOS: android/app/build.gradle
4. Bump version to match iOS & package.json: android/app/build.gradle
5. Open the project in Android Studio:
@@ -444,7 +435,6 @@ Prerequisites: Android Studio with Java SDK installed
./gradlew clean
./gradlew build -Dlint.baselines.continue=true
cd -
npx cap run android
```
... or, to create the `aab` file, `bundle` instead of `build`:
@@ -478,7 +468,7 @@ At play.google.com/console:
- Note that if you add testers, you have to go to "Publishing Overview" and send those changes or your (closed) testers won't see it.
## First-time Android Configuration for deep links
## Android Configuration for deep links
You must add the following intent filter to the `android/app/src/main/AndroidManifest.xml` file:
@@ -490,3 +480,5 @@ You must add the following intent filter to the `android/app/src/main/AndroidMan
<data android:scheme="timesafari" />
</intent-filter>
```
... though when we tried that most recently it failed to 'build' the APK with: http(s) scheme and host attribute are missing, but are required for Android App Links [AppLinkUrlError]

View File

@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.8]
### Added
- /deep-link/ path for URLs that are shared with people
### Changed
- External links now go to /deep-link/...
- Feed visuals now have arrow imagery from giver to receiver
## [0.4.7]
### Fixed

View File

@@ -31,8 +31,8 @@ android {
applicationId "app.timesafari.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 26
versionName "0.5.1"
versionCode 34
versionName "0.5.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View File

@@ -100,6 +100,7 @@ try {
- `src/interfaces/deepLinks.ts`: Type definitions and validation schemas
- `src/services/deepLinks.ts`: Deep link processing service
- `src/main.capacitor.ts`: Capacitor integration
- `src/views/DeepLinkRedirectView.vue`: Page to handle links to both mobile and web
## Type Safety Examples

View File

@@ -403,7 +403,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_TEAM = GM3FS5JQPH;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -413,7 +413,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.5.1;
MARKETING_VERSION = 0.5.8;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -430,7 +430,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_TEAM = GM3FS5JQPH;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -440,7 +440,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.5.1;
MARKETING_VERSION = 0.5.8;
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";

View File

@@ -49,5 +49,16 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>app.timesafari</string>
<key>CFBundleURLSchemes</key>
<array>
<string>timesafari</string>
</array>
</dict>
</array>
</dict>
</plist>

579
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "timesafari",
"version": "0.4.8",
"version": "0.5.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "timesafari",
"version": "0.4.8",
"version": "0.5.8",
"dependencies": {
"@capacitor-community/sqlite": "6.0.2",
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
@@ -3835,9 +3835,9 @@
}
},
"node_modules/@electron/asar/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4524,9 +4524,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6686,9 +6686,9 @@
}
},
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6983,6 +6983,29 @@
"integrity": "sha512-meL9DERHj+fFVWoOX9fXqfcYcSpUfSYJPcFvDPKrxitICbwAoWR+Ut4j5NO9zAT917HUHLQmqzQbAsGNHlDcxQ==",
"license": "Apache-2.0 OR MIT"
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -7839,9 +7862,9 @@
}
},
"node_modules/@react-native/assets-registry": {
"version": "0.79.3",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.3.tgz",
"integrity": "sha512-Vy8DQXCJ21YSAiHxrNBz35VqVlZPpRYm50xRTWRf660JwHuJkFQG8cUkrLzm7AUriqUXxwpkQHcY+b0ibw9ejQ==",
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.80.0.tgz",
"integrity": "sha512-MlScsKAz99zoYghe5Rf5mUqsqz2rMB02640NxtPtBMSHNdGxxRlWu/pp1bFexDa1DYJwyIjnLgt3Z/Y90ikHfw==",
"license": "MIT",
"optional": true,
"peer": true,
@@ -7947,20 +7970,20 @@
}
},
"node_modules/@react-native/community-cli-plugin": {
"version": "0.79.3",
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.79.3.tgz",
"integrity": "sha512-N/+p4HQqN4yK6IRzn7OgMvUIcrmEWkecglk1q5nj+AzNpfIOzB+mqR20SYmnPfeXF+mZzYCzRANb3KiM+WsSDA==",
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.80.0.tgz",
"integrity": "sha512-uadfVvzZfz5tGpqwslL12i+rELK9m6cLhtqICX0JQvS7Bu12PJwrozhKzEzIYwN9i3wl2dWrKDUr08izt7S9Iw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@react-native/dev-middleware": "0.79.3",
"@react-native/dev-middleware": "0.80.0",
"chalk": "^4.0.0",
"debug": "^2.2.0",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"metro": "^0.82.0",
"metro-config": "^0.82.0",
"metro-core": "^0.82.0",
"metro": "^0.82.2",
"metro-config": "^0.82.2",
"metro-core": "^0.82.2",
"semver": "^7.1.3"
},
"engines": {
@@ -7975,25 +7998,97 @@
}
}
},
"node_modules/@react-native/community-cli-plugin/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"node_modules/@react-native/community-cli-plugin/node_modules/@react-native/debugger-frontend": {
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.80.0.tgz",
"integrity": "sha512-lpu9Z3xtKUaKFvEcm5HSgo1KGfkDa/W3oZHn22Zy0WQ9MiOu2/ar1txgd1wjkoNiK/NethKcRdCN7mqnc6y2mA==",
"license": "BSD-3-Clause",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@react-native/community-cli-plugin/node_modules/@react-native/dev-middleware": {
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.80.0.tgz",
"integrity": "sha512-lLyTnJ687A5jF3fn8yR/undlCis3FG+N/apQ+Q0Lcl+GV6FsZs0U5H28YmL6lZtjOj4TLek6uGPMPmZasHx7cQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"ms": "2.0.0"
"@isaacs/ttlcache": "^1.4.1",
"@react-native/debugger-frontend": "0.80.0",
"chrome-launcher": "^0.15.2",
"chromium-edge-launcher": "^0.2.0",
"connect": "^3.6.5",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"nullthrows": "^1.1.1",
"open": "^7.0.3",
"serve-static": "^1.16.2",
"ws": "^6.2.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@react-native/community-cli-plugin/node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@react-native/community-cli-plugin/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/@react-native/community-cli-plugin/node_modules/open": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@react-native/community-cli-plugin/node_modules/ws": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"async-limiter": "~1.0.0"
}
},
"node_modules/@react-native/debugger-frontend": {
"version": "0.79.3",
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.79.3.tgz",
@@ -8078,9 +8173,9 @@
}
},
"node_modules/@react-native/gradle-plugin": {
"version": "0.79.3",
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.79.3.tgz",
"integrity": "sha512-imfpZLhNBc9UFSzb/MOy2tNcIBHqVmexh/qdzw83F75BmUtLb/Gs1L2V5gw+WI1r7RqDILbWk7gXB8zUllwd+g==",
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.80.0.tgz",
"integrity": "sha512-drmS68rabSMOuDD+YsAY2luNT8br82ycodSDORDqAg7yWQcieHMp4ZUOcdOi5iW+JCqobablT/b6qxcrBg+RaA==",
"license": "MIT",
"optional": true,
"peer": true,
@@ -8089,9 +8184,9 @@
}
},
"node_modules/@react-native/js-polyfills": {
"version": "0.79.3",
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.79.3.tgz",
"integrity": "sha512-PEBtg6Kox6KahjCAch0UrqCAmHiNLEbp2SblUEoFAQnov4DSxBN9safh+QSVaCiMAwLjvNfXrJyygZz60Dqz3Q==",
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.80.0.tgz",
"integrity": "sha512-dMX7IcBuwghySTgIeK8q03tYz/epg5ScGmJEfBQAciuhzMDMV1LBR/9wwdgD73EXM/133yC5A+TlHb3KQil4Ew==",
"license": "MIT",
"optional": true,
"peer": true,
@@ -8108,9 +8203,9 @@
"peer": true
},
"node_modules/@react-native/virtualized-lists": {
"version": "0.79.3",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.3.tgz",
"integrity": "sha512-/0rRozkn+iIHya2vnnvprDgT7QkfI54FLrACAN3BLP7MRlfOIGOrZsXpRLndnLBVnjNzkcre84i1RecjoXnwIA==",
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.80.0.tgz",
"integrity": "sha512-d9zZdPS/ZRexVAkxo1eRp85U7XnnEpXA1ZpSomRKxBuStYKky1YohfEX5YD5MhphemKK24tT7JR4UhaLlmeX8Q==",
"license": "MIT",
"optional": true,
"peer": true,
@@ -8217,9 +8312,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz",
"integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz",
"integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==",
"cpu": [
"arm"
],
@@ -8231,9 +8326,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz",
"integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz",
"integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==",
"cpu": [
"arm64"
],
@@ -8271,9 +8366,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz",
"integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz",
"integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==",
"cpu": [
"arm64"
],
@@ -8285,9 +8380,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz",
"integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz",
"integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==",
"cpu": [
"x64"
],
@@ -8299,9 +8394,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz",
"integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz",
"integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==",
"cpu": [
"arm"
],
@@ -8313,9 +8408,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz",
"integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz",
"integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==",
"cpu": [
"arm"
],
@@ -8353,9 +8448,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz",
"integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz",
"integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==",
"cpu": [
"loong64"
],
@@ -8367,9 +8462,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz",
"integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz",
"integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==",
"cpu": [
"ppc64"
],
@@ -8381,9 +8476,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz",
"integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz",
"integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==",
"cpu": [
"riscv64"
],
@@ -8395,9 +8490,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz",
"integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz",
"integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==",
"cpu": [
"riscv64"
],
@@ -8409,9 +8504,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz",
"integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz",
"integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==",
"cpu": [
"s390x"
],
@@ -8462,9 +8557,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz",
"integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz",
"integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==",
"cpu": [
"ia32"
],
@@ -8874,9 +8969,9 @@
}
},
"node_modules/@stencil/core": {
"version": "4.33.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.35.0.tgz",
"integrity": "sha512-x0IFtj7IJStK+ZqIkhReWbiC0UMjMJnNXV8OXG+DCLDExZaVaxL3MLuq6BJBBcQ1MHZduTHDv3Iz0Zshoj3zjQ==",
"license": "MIT",
"bin": {
"stencil": "bin/stencil"
@@ -11184,13 +11279,13 @@
}
},
"node_modules/app-builder-lib/node_modules/minimatch": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
@@ -11612,9 +11707,9 @@
}
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -12199,9 +12294,9 @@
}
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -12648,9 +12743,9 @@
}
},
"node_modules/cacache/node_modules/rimraf/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12868,9 +12963,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001721",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz",
"integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==",
"version": "1.0.30001723",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz",
"integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==",
"devOptional": true,
"funding": [
{
@@ -14507,9 +14602,9 @@
}
},
"node_modules/decode-named-character-reference": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
"integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
"integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -14931,9 +15026,9 @@
}
},
"node_modules/dir-compare/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15347,9 +15442,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.166",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz",
"integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==",
"version": "1.5.167",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz",
"integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==",
"devOptional": true,
"license": "ISC"
},
@@ -15865,9 +15960,9 @@
}
},
"node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -16079,9 +16174,9 @@
}
},
"node_modules/ethers": {
"version": "6.14.3",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.3.tgz",
"integrity": "sha512-qq7ft/oCJohoTcsNPFaXSQUm457MA5iWqkf1Mb11ujONdg7jBI6sAOrHaTi3j0CBqIGFSCeR/RMc+qwRRub7IA==",
"version": "6.14.4",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.4.tgz",
"integrity": "sha512-Jm/dzRs2Z9iBrT6e9TvGxyb5YVKAPLlpna7hjxH7KH/++DSh2T/JVmQUv7iHI5E55hDbp/gEVvstWYXVxXFzsA==",
"funding": [
{
"type": "individual",
@@ -16178,21 +16273,21 @@
"license": "MIT"
},
"node_modules/ethr-did": {
"version": "3.0.37",
"resolved": "https://registry.npmjs.org/ethr-did/-/ethr-did-3.0.37.tgz",
"integrity": "sha512-L9UUhAS8B1T7jTRdKLwAt514lx2UrJebJK7uc6UU4AJ9RhY8Vcfwc93Ux82jREE7yvvqDPXsVNH+lS3aw18a9A==",
"version": "3.0.38",
"resolved": "https://registry.npmjs.org/ethr-did/-/ethr-did-3.0.38.tgz",
"integrity": "sha512-gUxtErXVOQUJf+bmnxRdSJdlU9aFbQSBNaJCYGt+PLqw6l4qqInTfMRiWpwe/brhRtdjE+64tnayOVk8ataeQA==",
"license": "Apache-2.0",
"dependencies": {
"did-jwt": "^8.0.0",
"did-resolver": "^4.1.0",
"ethers": "^6.8.1",
"ethr-did-resolver": "11.0.3"
"ethr-did-resolver": "11.0.4"
}
},
"node_modules/ethr-did-resolver": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/ethr-did-resolver/-/ethr-did-resolver-11.0.3.tgz",
"integrity": "sha512-lQ1T/SZfgR6Kp05/GSIXnMELxQ5H6M6OCTH4wBTVSAgHzbJiDNVIYWzg/c+NniIM88B0ViAi4CaiCHaiUlvPQg==",
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/ethr-did-resolver/-/ethr-did-resolver-11.0.4.tgz",
"integrity": "sha512-EJ/dL2QsFzvhBJd0nlPFjma3bxpQOWyp2TytQZyAeqi6SfZ4ALCB0VaA4dSeT4T8ZtI2pzs/sD7t/7A0584J6Q==",
"license": "Apache-2.0",
"dependencies": {
"did-resolver": "^4.1.0",
@@ -17617,9 +17712,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -19127,9 +19222,9 @@
}
},
"node_modules/jake/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -24054,9 +24149,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
"integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz",
"integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==",
"funding": [
{
"type": "opencollective",
@@ -24954,44 +25049,43 @@
"peer": true
},
"node_modules/react-native": {
"version": "0.79.3",
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.79.3.tgz",
"integrity": "sha512-EzH1+9gzdyEo9zdP6u7Sh3Jtf5EOMwzy+TK65JysdlgAzfEVfq4mNeXcAZ6SmD+CW6M7ARJbvXLyTD0l2S5rpg==",
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.80.0.tgz",
"integrity": "sha512-b9K1ygb2MWCBtKAodKmE3UsbUuC29Pt4CrJMR0ocTA8k+8HJQTPleBPDNKL4/p0P01QO9aL/gZUddoxHempLow==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@jest/create-cache-key-function": "^29.7.0",
"@react-native/assets-registry": "0.79.3",
"@react-native/codegen": "0.79.3",
"@react-native/community-cli-plugin": "0.79.3",
"@react-native/gradle-plugin": "0.79.3",
"@react-native/js-polyfills": "0.79.3",
"@react-native/normalize-colors": "0.79.3",
"@react-native/virtualized-lists": "0.79.3",
"@react-native/assets-registry": "0.80.0",
"@react-native/codegen": "0.80.0",
"@react-native/community-cli-plugin": "0.80.0",
"@react-native/gradle-plugin": "0.80.0",
"@react-native/js-polyfills": "0.80.0",
"@react-native/normalize-colors": "0.80.0",
"@react-native/virtualized-lists": "0.80.0",
"abort-controller": "^3.0.0",
"anser": "^1.4.9",
"ansi-regex": "^5.0.0",
"babel-jest": "^29.7.0",
"babel-plugin-syntax-hermes-parser": "0.25.1",
"babel-plugin-syntax-hermes-parser": "0.28.1",
"base64-js": "^1.5.1",
"chalk": "^4.0.0",
"commander": "^12.0.0",
"event-target-shim": "^5.0.1",
"flow-enums-runtime": "^0.0.6",
"glob": "^7.1.1",
"invariant": "^2.2.4",
"jest-environment-node": "^29.7.0",
"memoize-one": "^5.0.0",
"metro-runtime": "^0.82.0",
"metro-source-map": "^0.82.0",
"metro-runtime": "^0.82.2",
"metro-source-map": "^0.82.2",
"nullthrows": "^1.1.1",
"pretty-format": "^29.7.0",
"promise": "^8.3.0",
"react-devtools-core": "^6.1.1",
"react-refresh": "^0.14.0",
"regenerator-runtime": "^0.13.2",
"scheduler": "0.25.0",
"scheduler": "0.26.0",
"semver": "^7.1.3",
"stacktrace-parser": "^0.1.10",
"whatwg-fetch": "^3.0.0",
@@ -25005,8 +25099,8 @@
"node": ">=18"
},
"peerDependencies": {
"@types/react": "^19.0.0",
"react": "^19.0.0"
"@types/react": "^19.1.0",
"react": "^19.1.0"
},
"peerDependenciesMeta": {
"@types/react": {
@@ -25039,14 +25133,46 @@
"react-native": "*"
}
},
"node_modules/react-native/node_modules/@react-native/codegen": {
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.80.0.tgz",
"integrity": "sha512-X9TsPgytoUkNrQjzAZh4dXa4AuouvYT0NzYyvnjw1ry4LESCZtKba+eY4x3+M30WPR52zjgu+UFL//14BSdCCA==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"glob": "^7.1.1",
"hermes-parser": "0.28.1",
"invariant": "^2.2.4",
"nullthrows": "^1.1.1",
"yargs": "^17.6.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/react-native/node_modules/@react-native/normalize-colors": {
"version": "0.79.3",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.3.tgz",
"integrity": "sha512-T75NIQPRFCj6DFMxtcVMJTZR+3vHXaUMSd15t+CkJpc5LnyX91GVaPxpRSAdjFh7m3Yppl5MpdjV/fntImheYQ==",
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.80.0.tgz",
"integrity": "sha512-bJZDSopadjJxMDvysc634eTfLL4w7cAx5diPe14Ez5l+xcKjvpfofS/1Ja14DlgdMJhxGd03MTXlrxoWust3zg==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.28.1.tgz",
"integrity": "sha512-meT17DOuUElMNsL5LZN56d+KBp22hb0EfxWfuPUeoSi54e40v1W4C2V36P75FpsH9fVEfDKpw5Nnkahc8haSsQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"hermes-parser": "0.28.1"
}
},
"node_modules/react-native/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
@@ -25058,6 +25184,25 @@
"node": ">=18"
}
},
"node_modules/react-native/node_modules/hermes-estree": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.28.1.tgz",
"integrity": "sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/react-native/node_modules/hermes-parser": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.28.1.tgz",
"integrity": "sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"hermes-estree": "0.28.1"
}
},
"node_modules/react-native/node_modules/ws": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
@@ -25514,9 +25659,9 @@
}
},
"node_modules/replace/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -25960,15 +26105,15 @@
}
},
"node_modules/rimraf/node_modules/glob": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz",
"integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==",
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^4.0.1",
"minimatch": "^10.0.0",
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.0.3",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
@@ -26010,13 +26155,13 @@
}
},
"node_modules/rimraf/node_modules/minimatch": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
@@ -26094,9 +26239,9 @@
}
},
"node_modules/rollup": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz",
"integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz",
"integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -26110,33 +26255,33 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.42.0",
"@rollup/rollup-android-arm64": "4.42.0",
"@rollup/rollup-darwin-arm64": "4.42.0",
"@rollup/rollup-darwin-x64": "4.42.0",
"@rollup/rollup-freebsd-arm64": "4.42.0",
"@rollup/rollup-freebsd-x64": "4.42.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.42.0",
"@rollup/rollup-linux-arm-musleabihf": "4.42.0",
"@rollup/rollup-linux-arm64-gnu": "4.42.0",
"@rollup/rollup-linux-arm64-musl": "4.42.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.42.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.42.0",
"@rollup/rollup-linux-riscv64-gnu": "4.42.0",
"@rollup/rollup-linux-riscv64-musl": "4.42.0",
"@rollup/rollup-linux-s390x-gnu": "4.42.0",
"@rollup/rollup-linux-x64-gnu": "4.42.0",
"@rollup/rollup-linux-x64-musl": "4.42.0",
"@rollup/rollup-win32-arm64-msvc": "4.42.0",
"@rollup/rollup-win32-ia32-msvc": "4.42.0",
"@rollup/rollup-win32-x64-msvc": "4.42.0",
"@rollup/rollup-android-arm-eabi": "4.43.0",
"@rollup/rollup-android-arm64": "4.43.0",
"@rollup/rollup-darwin-arm64": "4.43.0",
"@rollup/rollup-darwin-x64": "4.43.0",
"@rollup/rollup-freebsd-arm64": "4.43.0",
"@rollup/rollup-freebsd-x64": "4.43.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.43.0",
"@rollup/rollup-linux-arm-musleabihf": "4.43.0",
"@rollup/rollup-linux-arm64-gnu": "4.43.0",
"@rollup/rollup-linux-arm64-musl": "4.43.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.43.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.43.0",
"@rollup/rollup-linux-riscv64-gnu": "4.43.0",
"@rollup/rollup-linux-riscv64-musl": "4.43.0",
"@rollup/rollup-linux-s390x-gnu": "4.43.0",
"@rollup/rollup-linux-x64-gnu": "4.43.0",
"@rollup/rollup-linux-x64-musl": "4.43.0",
"@rollup/rollup-win32-arm64-msvc": "4.43.0",
"@rollup/rollup-win32-ia32-msvc": "4.43.0",
"@rollup/rollup-win32-x64-msvc": "4.43.0",
"fsevents": "~2.3.2"
}
},
"node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz",
"integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz",
"integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==",
"cpu": [
"arm64"
],
@@ -26148,9 +26293,9 @@
]
},
"node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz",
"integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz",
"integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==",
"cpu": [
"x64"
],
@@ -26162,9 +26307,9 @@
]
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz",
"integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz",
"integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==",
"cpu": [
"arm64"
],
@@ -26176,9 +26321,9 @@
]
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz",
"integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz",
"integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==",
"cpu": [
"arm64"
],
@@ -26190,9 +26335,9 @@
]
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz",
"integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz",
"integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==",
"cpu": [
"x64"
],
@@ -26204,9 +26349,9 @@
]
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz",
"integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz",
"integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==",
"cpu": [
"x64"
],
@@ -26218,9 +26363,9 @@
]
},
"node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz",
"integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz",
"integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==",
"cpu": [
"arm64"
],
@@ -26232,9 +26377,9 @@
]
},
"node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz",
"integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==",
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz",
"integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==",
"cpu": [
"x64"
],
@@ -26410,9 +26555,9 @@
"license": "ISC"
},
"node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"license": "MIT",
"optional": true,
"peer": true
@@ -26424,9 +26569,9 @@
"license": "MIT"
},
"node_modules/sdp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz",
"integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==",
"license": "MIT"
},
"node_modules/secp256k1": {
@@ -28495,9 +28640,9 @@
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"optional": true,
"peer": true,
@@ -31045,9 +31190,9 @@
}
},
"node_modules/zod": {
"version": "3.25.58",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.58.tgz",
"integrity": "sha512-DVLmMQzSZwNYzQoMaM3MQWnxr2eq+AtM9Hx3w1/Yl0pH8sLTSjN4jGP7w6f7uand6Hw44tsnSu1hz1AOA6qI2Q==",
"version": "3.25.64",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz",
"integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"

View File

@@ -1,6 +1,6 @@
{
"name": "timesafari",
"version": "0.5.1",
"version": "0.5.8",
"description": "Time Safari Application",
"author": {
"name": "Time Safari Team"

View File

@@ -321,7 +321,7 @@ export default class GiftedDialog extends Vue {
);
if (!result.success) {
const errorMessage = this.getGiveCreationErrorMessage(result);
const errorMessage = result.error;
logger.error("Error with give creation result:", result);
this.$notify(
{
@@ -367,19 +367,6 @@ export default class GiftedDialog extends Vue {
// Helper functions for readability
/**
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
* @returns best guess at an error message
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getGiveCreationErrorMessage(result: any) {
return (
result.error?.userMessage ||
result.error?.error ||
result.response?.data?.error?.message
);
}
explainData() {
this.$notify(
{

View File

@@ -48,12 +48,15 @@
<span>
{{ didInfo(visDid) }}
<span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)">
<a :href="`/did/${visDid}`" class="text-blue-500">
<router-link
:to="{ path: '/did/' + encodeURIComponent(visDid) }"
class="text-blue-500"
>
<font-awesome
icon="arrow-up-right-from-square"
class="fa-fw"
/>
</a>
</router-link>
</span>
</span>
</div>
@@ -74,7 +77,7 @@
If you'd like an introduction,
<a
class="text-blue-500"
@click="copyToClipboard('A link to this page', windowLocation)"
@click="copyToClipboard('A link to this page', deepLinkUrl)"
>click here to copy this page, paste it into a message, and ask if
they'll tell you more about the {{ roleName }}.</a
>
@@ -101,7 +104,7 @@ import * as R from "ramda";
import { useClipboard } from "@vueuse/core";
import { Contact } from "../db/tables/contacts";
import * as serverUtil from "../libs/endorserServer";
import { NotificationIface } from "../constants/app";
import { APP_SERVER, NotificationIface } from "../constants/app";
@Component
export default class HiddenDidDialog extends Vue {
@@ -114,7 +117,8 @@ export default class HiddenDidDialog extends Vue {
activeDid = "";
allMyDids: Array<string> = [];
canShare = false;
windowLocation = window.location.href;
deepLinkPathSuffix = "";
deepLinkUrl = window.location.href; // this is changed to a deep link in the setup
R = R;
serverUtil = serverUtil;
@@ -126,17 +130,21 @@ export default class HiddenDidDialog extends Vue {
}
open(
deepLinkPathSuffix: string,
roleName: string,
visibleToDids: string[],
allContacts: Array<Contact>,
activeDid: string,
allMyDids: Array<string>,
) {
this.deepLinkPathSuffix = deepLinkPathSuffix;
this.roleName = roleName;
this.visibleToDids = visibleToDids;
this.allContacts = allContacts;
this.activeDid = activeDid;
this.allMyDids = allMyDids;
this.deepLinkUrl = APP_SERVER + "/deep-link/" + this.deepLinkPathSuffix;
this.isOpen = true;
}
@@ -170,11 +178,11 @@ export default class HiddenDidDialog extends Vue {
}
onClickShareClaim() {
this.copyToClipboard("A link to this page", this.windowLocation);
this.copyToClipboard("A link to this page", this.deepLinkUrl);
window.navigator.share({
title: "Help Connect Me",
text: "I'm trying to find the people who recorded this. Can you help me?",
url: this.windowLocation,
url: this.deepLinkUrl,
});
}
}

View File

@@ -83,10 +83,7 @@
import { Vue, Component, Prop } from "vue-facing-decorator";
import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
import {
createAndSubmitOffer,
serverMessageForUser,
} from "../libs/endorserServer";
import { createAndSubmitOffer } from "../libs/endorserServer";
import * as libsUtil from "../libs/util";
import * as databaseUtil from "../db/databaseUtil";
import { retrieveSettingsForActiveAccount } from "../db/index";
@@ -250,7 +247,7 @@ export default class OfferDialog extends Vue {
);
if (!result.success) {
const errorMessage = this.getOfferCreationErrorMessage(result);
const errorMessage = result.error;
logger.error("Error with offer creation result:", result);
this.$notify(
{
@@ -290,21 +287,6 @@ export default class OfferDialog extends Vue {
);
}
}
// Helper functions for readability
/**
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
* @returns best guess at an error message
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getOfferCreationErrorMessage(result: any) {
return (
serverMessageForUser(result) ||
result.error?.userMessage ||
result.error?.error
);
}
}
</script>

View File

@@ -38,14 +38,13 @@ export default class TopMessage extends Vue {
settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER
) {
const didPrefix = settings.activeDid?.slice(11, 15);
this.message = "You're linked to a non-prod server, user " + didPrefix;
this.message = "You're not using prod, user " + didPrefix;
} else if (
settings.warnIfProdServer &&
settings.apiServer === AppString.PROD_ENDORSER_API_SERVER
) {
const didPrefix = settings.activeDid?.slice(11, 15);
this.message =
"You're linked to the production server, user " + didPrefix;
this.message = "You are using prod, user " + didPrefix;
}
} catch (err: unknown) {
this.$notify(

View File

@@ -219,9 +219,9 @@ export async function logConsoleAndDb(
isError = false,
): Promise<void> {
if (isError) {
logger.error(`${new Date().toISOString()} ${message}`);
logger.error(`${new Date().toISOString()}`, message);
} else {
logger.log(`${new Date().toISOString()} ${message}`);
logger.log(`${new Date().toISOString()}`, message);
}
await logToDb(message);
}

View File

@@ -1,6 +1,4 @@
import { AxiosResponse } from "axios";
import { GiverReceiverInputInfo } from "../libs/util";
import { ErrorResult, ResultWithType } from "./common";
export interface GiverOutputInfo {
action: string;
@@ -47,12 +45,3 @@ export interface ProviderInfo {
*/
linkConfirmed: boolean;
}
// Type for createAndSubmitClaim result
export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult;
// Update SuccessResult to use ClaimResult
export interface SuccessResult extends ResultWithType {
type: "success";
response: AxiosResponse<ClaimResult>;
}

View File

@@ -15,10 +15,6 @@ export interface GenericCredWrapper<T extends GenericVerifiableCredential> {
publicUrls?: Record<string, string>;
}
export interface ResultWithType {
type: string;
}
export interface ErrorResponse {
error?: {
message?: string;
@@ -30,11 +26,6 @@ export interface InternalError {
userMessage?: string;
}
export interface ErrorResult extends ResultWithType {
type: "error";
error: InternalError;
}
export interface KeyMeta {
did: string;
publicKeyHex: string;

View File

@@ -29,18 +29,17 @@ import { z } from "zod";
// Add a union type of all valid route paths
export const VALID_DEEP_LINK_ROUTES = [
"user-profile",
"project-details",
"onboard-meeting-setup",
"invite-one-accept",
"contact-import",
"confirm-gift",
// note that similar lists are below in deepLinkSchemas and in src/services/deepLinks.ts
"claim",
"claim-cert",
"claim-add-raw",
"contact-edit",
"contacts",
"claim-cert",
"confirm-gift",
"contact-import",
"did",
"invite-one-accept",
"onboard-meeting-setup",
"project",
"user-profile",
] as const;
// Create a type from the array
@@ -58,44 +57,39 @@ export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES);
// Parameter validation schemas for each route type
export const deepLinkSchemas = {
"user-profile": z.object({
id: z.string(),
}),
"project-details": z.object({
id: z.string(),
}),
"onboard-meeting-setup": z.object({
id: z.string(),
}),
"invite-one-accept": z.object({
id: z.string(),
}),
"contact-import": z.object({
jwt: z.string(),
}),
"confirm-gift": z.object({
id: z.string(),
}),
// note that similar lists are above in VALID_DEEP_LINK_ROUTES and in src/services/deepLinks.ts
claim: z.object({
id: z.string(),
}),
"claim-cert": z.object({
id: z.string(),
}),
"claim-add-raw": z.object({
id: z.string(),
claim: z.string().optional(),
claimJwtId: z.string().optional(),
}),
"contact-edit": z.object({
did: z.string(),
"claim-cert": z.object({
id: z.string(),
}),
contacts: z.object({
contacts: z.string(), // JSON string of contacts array
"confirm-gift": z.object({
id: z.string(),
}),
"contact-import": z.object({
jwt: z.string(),
}),
did: z.object({
did: z.string(),
}),
"invite-one-accept": z.object({
jwt: z.string(),
}),
"onboard-meeting-setup": z.object({
id: z.string(),
}),
project: z.object({
id: z.string(),
}),
"user-profile": z.object({
id: z.string(),
}),
};
export type DeepLinkParams = {

View File

@@ -1,5 +1,6 @@
export type {
// From common.ts
CreateAndSubmitClaimResult,
GenericCredWrapper,
GenericVerifiableCredential,
KeyMeta,
@@ -18,11 +19,6 @@ export type {
RegisterActionClaim,
} from "./claims";
export type {
// From claims-result.ts
CreateAndSubmitClaimResult,
} from "./claims-result";
export type {
// From records.ts
PlanSummaryRecord,

View File

@@ -979,7 +979,7 @@ export const createAndSubmitConfirmation = async (
handleId: string | undefined,
apiServer: string,
axios: Axios,
) => {
): Promise<CreateAndSubmitClaimResult> => {
const goodClaim = removeSchemaContext(
removeVisibleToDids(
addLastClaimOrHandleAsIdIfMissing(claim, lastClaimId, handleId),
@@ -1074,7 +1074,8 @@ export async function generateEndorserJwtUrlForAccount(
const vcJwt = await createEndorserJwtForDid(account.did, contactInfo);
const viewPrefix = APP_SERVER + CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI;
const viewPrefix =
APP_SERVER + "/deep-link" + CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI;
return viewPrefix + vcJwt;
}

View File

@@ -882,6 +882,71 @@ export const contactToCsvLine = (contact: Contact): string => {
return fields.join(",");
};
/**
* Parses a CSV line into a Contact object. See contactToCsvLine for the format.
* @param lineRaw - The CSV line to parse
* @returns A Contact object
*/
export const csvLineToContact = (lineRaw: string): Contact => {
// Note that Endorser Mobile puts name first, then did, etc.
let line = lineRaw.trim();
let did, publicKeyInput, seesMe, registered;
let name;
let commaPos1 = -1;
if (line.startsWith('"')) {
let doubleDoubleQuotePos = line.lastIndexOf('""') + 2;
if (doubleDoubleQuotePos === -1) {
doubleDoubleQuotePos = 1;
}
const quote2Pos = line.indexOf('"', doubleDoubleQuotePos);
if (quote2Pos > -1) {
commaPos1 = line.indexOf(",", quote2Pos);
name = line.substring(1, quote2Pos).trim();
name = name.replace(/""/g, '"');
} else {
// something is weird with one " to start, so ignore it and start after "
line = line.substring(1);
commaPos1 = line.indexOf(",");
name = line.substring(0, commaPos1).trim();
}
} else {
commaPos1 = line.indexOf(",");
name = line.substring(0, commaPos1).trim();
}
if (commaPos1 > -1) {
did = line.substring(commaPos1 + 1).trim();
const commaPos2 = line.indexOf(",", commaPos1 + 1);
if (commaPos2 > -1) {
did = line.substring(commaPos1 + 1, commaPos2).trim();
publicKeyInput = line.substring(commaPos2 + 1).trim();
const commaPos3 = line.indexOf(",", commaPos2 + 1);
if (commaPos3 > -1) {
publicKeyInput = line.substring(commaPos2 + 1, commaPos3).trim();
seesMe = line.substring(commaPos3 + 1).trim() == "true";
const commaPos4 = line.indexOf(",", commaPos3 + 1);
if (commaPos4 > -1) {
seesMe = line.substring(commaPos3 + 1, commaPos4).trim() == "true";
registered = line.substring(commaPos4 + 1).trim() == "true";
}
}
}
}
// help with potential mistakes while this sharing requires copy-and-paste
let publicKeyBase64 = publicKeyInput;
if (publicKeyBase64 && /^[0-9A-Fa-f]{66}$/i.test(publicKeyBase64)) {
// it must be all hex (compressed public key), so convert
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
}
const newContact: Contact = {
did: did || "",
name,
publicKeyBase64,
seesMe,
registered,
};
return newContact;
};
/**
* Interface for the JSON export format of database tables
*/

View File

@@ -34,8 +34,7 @@ import router from "./router";
import { handleApiError } from "./services/api";
import { AxiosError } from "axios";
import { DeepLinkHandler } from "./services/deepLinks";
import { logConsoleAndDb } from "./db/databaseUtil";
import { logger } from "./utils/logger";
import { logger, safeStringify } from "./utils/logger";
logger.log("[Capacitor] Starting initialization");
logger.log("[Capacitor] Platform:", process.env.VITE_PLATFORM);
@@ -72,10 +71,10 @@ const handleDeepLink = async (data: { url: string }) => {
await router.isReady();
await deepLinkHandler.handleDeepLink(data.url);
} catch (error) {
logConsoleAndDb("[DeepLink] Error handling deep link: " + error, true);
logger.error("[DeepLink] Error handling deep link: ", error);
handleApiError(
{
message: error instanceof Error ? error.message : String(error),
message: error instanceof Error ? error.message : safeStringify(error),
} as AxiosError,
"deep-link",
);

View File

@@ -83,6 +83,11 @@ const routes: Array<RouteRecordRaw> = [
name: "discover",
component: () => import("../views/DiscoverView.vue"),
},
{
path: "/deep-link/:path*",
name: "deep-link",
component: () => import("../views/DeepLinkRedirectView.vue"),
},
{
path: "/gifted-details",
name: "gifted-details",

View File

@@ -6,7 +6,7 @@
*/
import { AxiosError } from "axios";
import { logger } from "../utils/logger";
import { logger, safeStringify } from "../utils/logger";
/**
* Handles API errors with platform-specific logging and error processing.
@@ -37,7 +37,8 @@ import { logger } from "../utils/logger";
*/
export const handleApiError = (error: AxiosError, endpoint: string) => {
if (process.env.VITE_PLATFORM === "capacitor") {
logger.error(`[Capacitor API Error] ${endpoint}:`, {
const endpointStr = safeStringify(endpoint); // we've seen this as an object in deep links
logger.error(`[Capacitor API Error] ${endpointStr}:`, {
message: error.message,
status: error.response?.status,
data: error.response?.data,

View File

@@ -27,18 +27,16 @@
* timesafari://<route>[/<param>][?queryParam1=value1&queryParam2=value2]
*
* Supported Routes:
* - user-profile: View user profile
* - project-details: View project details
* - onboard-meeting-setup: Setup onboarding meeting
* - invite-one-accept: Accept invitation
* - contact-import: Import contacts
* - confirm-gift: Confirm gift
* - claim: View claim
* - claim-cert: View claim certificate
* - claim-add-raw: Add raw claim
* - contact-edit: Edit contact
* - contacts: View contacts
* - claim-cert: View claim certificate
* - confirm-gift
* - contact-import: Import contacts
* - did: View DID
* - invite-one-accept: Accept invitation
* - onboard-meeting-members
* - project: View project details
* - user-profile: View user profile
*
* @example
* const handler = new DeepLinkHandler(router);
@@ -81,18 +79,17 @@ export class DeepLinkHandler {
string,
{ name: string; paramKey?: string }
> = {
"user-profile": { name: "user-profile" },
"project-details": { name: "project-details" },
"onboard-meeting-setup": { name: "onboard-meeting-setup" },
"invite-one-accept": { name: "invite-one-accept" },
"contact-import": { name: "contact-import" },
"confirm-gift": { name: "confirm-gift" },
// note that similar lists are in src/interfaces/deepLinks.ts
claim: { name: "claim" },
"claim-cert": { name: "claim-cert" },
"claim-add-raw": { name: "claim-add-raw" },
"contact-edit": { name: "contact-edit", paramKey: "did" },
contacts: { name: "contacts" },
"claim-cert": { name: "claim-cert" },
"confirm-gift": { name: "confirm-gift" },
"contact-import": { name: "contact-import", paramKey: "jwt" },
did: { name: "did", paramKey: "did" },
"invite-one-accept": { name: "invite-one-accept", paramKey: "jwt" },
"onboard-meeting-members": { name: "onboard-meeting-members" },
project: { name: "project" },
"user-profile": { name: "user-profile" },
};
/**
@@ -101,7 +98,7 @@ export class DeepLinkHandler {
*
* @param url - The deep link URL to parse (format: scheme://path[?query])
* @throws {DeepLinkError} If URL format is invalid
* @returns Parsed URL components (path, params, query)
* @returns Parsed URL components (path: string, params: {KEY: string}, query: {KEY: string})
*/
private parseDeepLink(url: string) {
const parts = url.split("://");
@@ -117,7 +114,16 @@ export class DeepLinkHandler {
});
const [path, queryString] = parts[1].split("?");
const [routePath, param] = path.split("/");
const [routePath, ...pathParams] = path.split("/");
// logger.info(
// "[DeepLink] Debug:",
// "Route Path:",
// routePath,
// "Path Params:",
// pathParams,
// "Query String:",
// queryString,
// );
// Validate route exists before proceeding
if (!this.ROUTE_MAP[routePath]) {
@@ -136,45 +142,14 @@ export class DeepLinkHandler {
}
const params: Record<string, string> = {};
if (param) {
if (pathParams) {
// Now we know routePath exists in ROUTE_MAP
const routeConfig = this.ROUTE_MAP[routePath];
params[routeConfig.paramKey ?? "id"] = param;
params[routeConfig.paramKey ?? "id"] = pathParams.join("/");
}
return { path: routePath, params, query };
}
/**
* Processes incoming deep links and routes them appropriately.
* Handles validation, error handling, and routing to the correct view.
*
* @param url - The deep link URL to process
* @throws {DeepLinkError} If URL processing fails
*/
async handleDeepLink(url: string): Promise<void> {
try {
logConsoleAndDb("[DeepLink] Processing URL: " + url, false);
const { path, params, query } = this.parseDeepLink(url);
// Ensure params is always a Record<string,string> by converting undefined to empty string
const sanitizedParams = Object.fromEntries(
Object.entries(params).map(([key, value]) => [key, value ?? ""]),
);
await this.validateAndRoute(path, sanitizedParams, query);
} catch (error) {
const deepLinkError = error as DeepLinkError;
logConsoleAndDb(
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.message}`,
true,
);
throw {
code: deepLinkError.code || "UNKNOWN_ERROR",
message: deepLinkError.message,
details: deepLinkError.details,
};
}
}
/**
* Routes the deep link to appropriate view with validated parameters.
* Validates route and parameters using Zod schemas before routing.
@@ -245,6 +220,39 @@ export class DeepLinkHandler {
code: "INVALID_PARAMETERS",
message: (error as Error).message,
details: error,
params: params,
query: query,
};
}
}
/**
* Processes incoming deep links and routes them appropriately.
* Handles validation, error handling, and routing to the correct view.
*
* @param url - The deep link URL to process
* @throws {DeepLinkError} If URL processing fails
*/
async handleDeepLink(url: string): Promise<void> {
try {
logConsoleAndDb("[DeepLink] Processing URL: " + url, false);
const { path, params, query } = this.parseDeepLink(url);
// Ensure params is always a Record<string,string> by converting undefined to empty string
const sanitizedParams = Object.fromEntries(
Object.entries(params).map(([key, value]) => [key, value ?? ""]),
);
await this.validateAndRoute(path, sanitizedParams, query);
} catch (error) {
const deepLinkError = error as DeepLinkError;
logConsoleAndDb(
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.message}`,
true,
);
throw {
code: deepLinkError.code || "UNKNOWN_ERROR",
message: deepLinkError.message,
details: deepLinkError.details,
};
}
}

View File

@@ -5,6 +5,7 @@ import {
CameraSource,
CameraDirection,
} from "@capacitor/camera";
import { Capacitor } from "@capacitor/core";
import { Share } from "@capacitor/share";
import {
SQLiteConnection,
@@ -247,7 +248,7 @@ export class CapacitorPlatformService implements PlatformService {
hasFileSystem: true,
hasCamera: true,
isMobile: true,
isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent),
isIOS: Capacitor.getPlatform() === "ios",
hasFileDownload: false,
needsFileHandlingInstructions: true,
isNativeApp: true,

View File

@@ -1,6 +1,6 @@
import { logToDb } from "../db/databaseUtil";
function safeStringify(obj: unknown) {
export function safeStringify(obj: unknown) {
const seen = new WeakSet();
return JSON.stringify(obj, (_key, value) => {
@@ -67,8 +67,9 @@ export const logger = {
// Errors will always be logged
// eslint-disable-next-line no-console
console.error(message, ...args);
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
logToDb(message + argsString);
const messageString = safeStringify(message);
const argsString = args.length > 0 ? safeStringify(args) : "";
logToDb(messageString + argsString);
},
};

View File

@@ -198,7 +198,7 @@ export default class ClaimAddRawView extends Vue {
this.apiServer,
this.axios,
);
if (result.type === "success") {
if (result.success) {
this.$notify(
{
group: "alert",

View File

@@ -46,23 +46,35 @@
</h2>
<div class="flex justify-center w-full">
<router-link
v-if="veriClaim.id"
:to="'/claim-cert/' + encodeURIComponent(veriClaim.id)"
class="text-blue-500 mt-2"
title="Printable Certificate"
title="View Printable Certificate"
>
<font-awesome
icon="square"
class="text-white bg-yellow-500 p-1"
/>
</router-link>
<button
v-if="veriClaim.id"
class="text-blue-500 ml-2 mt-2"
title="Copy Printable Certificate Link"
@click="
copyToClipboard(
'A link to the certificate page',
`${APP_SERVER}/deep-link/claim-cert/${veriClaim.id}`,
)
"
>
<font-awesome icon="link" class="text-yellow-500 p-1" />
</button>
</div>
<!-- show link icon to copy this URL to the clipboard -->
<div class="flex justify-end w-full">
<button
title="Copy Link"
@click="
copyToClipboard('A link to this page', window.location.href)
"
@click="copyToClipboard('A link to this page', windowDeepLink)"
>
<font-awesome icon="link" class="text-slate-500" />
</button>
@@ -292,12 +304,17 @@
<div class="text-sm">
{{ didInfo(confirmerId) }}
<span v-if="!serverUtil.isEmptyOrHiddenDid(confirmerId)">
<a :href="`/did/${confirmerId}`" class="text-blue-500">
<router-link
:to="{
path: '/did/' + encodeURIComponent(confirmerId),
}"
class="text-blue-500"
>
<font-awesome
icon="arrow-up-right-from-square"
class="fa-fw"
/>
</a>
</router-link>
</span>
</div>
</div>
@@ -329,12 +346,17 @@
<div class="text-sm">
{{ didInfo(confsVisibleTo) }}
<span v-if="!serverUtil.isEmptyOrHiddenDid(confsVisibleTo)">
<a :href="`/did/${confsVisibleTo}`" class="text-blue-500">
<router-link
:to="{
path: '/did/' + encodeURIComponent(confsVisibleTo),
}"
class="text-blue-500"
>
<font-awesome
icon="arrow-up-right-from-square"
class="fa-fw"
/>
</a>
</router-link>
</span>
</div>
</div>
@@ -394,7 +416,7 @@
contacts can see more details:
<a
class="text-blue-500"
@click="copyToClipboard('A link to this page', windowLocation)"
@click="copyToClipboard('A link to this page', windowDeepLink)"
>click to copy this page info</a
>
and see if they can make an introduction. Someone is connected to
@@ -417,7 +439,7 @@
If you'd like an introduction,
<a
class="text-blue-500"
@click="copyToClipboard('A link to this page', windowLocation)"
@click="copyToClipboard('A link to this page', windowDeepLink)"
>share this page with them and ask if they'll tell you more about
about the participants.</a
>
@@ -443,12 +465,17 @@
<span>
{{ didInfo(visDid) }}
<span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)">
<a :href="`/did/${visDid}`" class="text-blue-500">
<router-link
:to="{
path: '/did/' + encodeURIComponent(visDid),
}"
class="text-blue-500"
>
<font-awesome
icon="arrow-up-right-from-square"
class="fa-fw"
/>
</a>
</router-link>
</span>
<span v-if="veriClaim.publicUrls?.[visDid]"
>, found at&nbsp;<a
@@ -530,7 +557,7 @@ import { useClipboard } from "@vueuse/core";
import { GenericVerifiableCredential } from "../interfaces";
import GiftedDialog from "../components/GiftedDialog.vue";
import QuickNav from "../components/QuickNav.vue";
import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
import { APP_SERVER, NotificationIface, USE_DEXIE_DB } from "../constants/app";
import * as databaseUtil from "../db/databaseUtil";
import { db } from "../db/index";
import { logConsoleAndDb } from "../db/databaseUtil";
@@ -577,8 +604,9 @@ export default class ClaimView extends Vue {
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
veriClaimDump = "";
veriClaimDidsVisible: { [key: string]: string[] } = {};
windowLocation = window.location.href;
windowDeepLink = window.location.href; // changed in the setup for deep linking
APP_SERVER = APP_SERVER;
R = R;
yaml = yaml;
libsUtil = libsUtil;
@@ -655,6 +683,7 @@ export default class ClaimView extends Vue {
5000,
);
}
this.windowDeepLink = `${APP_SERVER}/deep-link/claim/${claimId}`;
this.canShare = !!navigator.share;
}
@@ -925,7 +954,7 @@ export default class ClaimView extends Vue {
this.apiServer,
this.axios,
);
if (result.type === "success") {
if (result.success) {
this.$notify(
{
group: "alert",
@@ -990,11 +1019,11 @@ export default class ClaimView extends Vue {
}
onClickShareClaim() {
this.copyToClipboard("A link to this page", this.windowLocation);
this.copyToClipboard("A link to this page", this.windowDeepLink);
window.navigator.share({
title: "Help Connect Me",
text: "I'm trying to find the people who recorded this. Can you help me?",
url: this.windowLocation,
url: this.windowDeepLink,
});
}

View File

@@ -407,14 +407,14 @@
</a>
</div>
<div class="mt-2 ml-2">
<a
<router-link
v-if="isRegistered"
class="text-blue-500 cursor-pointer"
:href="urlForNewGive"
:to="urlForNewGive"
>
<font-awesome icon="file-lines" />
Record a Give Similar to the Original
</a>
</router-link>
</div>
</div>
</div>
@@ -436,7 +436,7 @@ import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core";
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import QuickNav from "../components/QuickNav.vue";
import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
import { APP_SERVER, NotificationIface, USE_DEXIE_DB } from "../constants/app";
import { db, retrieveSettingsForActiveAccount } from "../db/index";
import { Contact } from "../db/tables/contacts";
import * as databaseUtil from "../db/databaseUtil";
@@ -494,7 +494,7 @@ export default class ConfirmGiftView extends Vue {
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
veriClaimDump = "";
veriClaimDidsVisible: { [key: string]: string[] } = {};
windowLocation = window.location.href;
windowLocation = window.location.href; // this is changed to a deep link in the setup
R = R;
yaml = yaml;
@@ -566,6 +566,9 @@ export default class ConfirmGiftView extends Vue {
}
const claimId = decodeURIComponent(pathParam);
this.windowLocation = APP_SERVER + "/deep-link/confirm-gift/" + claimId;
await this.loadClaim(claimId, this.activeDid);
}
@@ -676,12 +679,12 @@ export default class ConfirmGiftView extends Vue {
/**
* Add participant (giver/recipient) name & URL info
*/
this.giverName = this.didInfo(this.giveDetails?.agentDid);
if (this.giveDetails?.agentDid) {
this.giverName = this.didInfo(this.giveDetails.agentDid);
this.urlForNewGive += `&giverDid=${encodeURIComponent(this.giveDetails.agentDid)}&giverName=${encodeURIComponent(this.giverName)}`;
}
this.recipientName = this.didInfo(this.giveDetails?.recipientDid);
if (this.giveDetails?.recipientDid) {
this.recipientName = this.didInfo(this.giveDetails.recipientDid);
this.urlForNewGive += `&recipientDid=${encodeURIComponent(this.giveDetails.recipientDid)}&recipientName=${encodeURIComponent(this.recipientName)}`;
}
@@ -831,7 +834,7 @@ export default class ConfirmGiftView extends Vue {
this.apiServer,
this.axios,
);
if (result.type === "success") {
if (result.success) {
this.$notify(
{
group: "alert",

View File

@@ -104,6 +104,7 @@
</template>
<script lang="ts">
import { Buffer } from "buffer/";
import QRCodeVue3 from "qr-code-generator-vue3";
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
@@ -117,14 +118,20 @@ import { db } from "../db/index";
import { Contact } from "../db/tables/contacts";
import { getContactJwtFromJwtUrl } from "../libs/crypto";
import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc";
import * as libsUtil from "../libs/util";
import { retrieveSettingsForActiveAccount } from "../db/index";
import * as databaseUtil from "../db/databaseUtil";
import { setVisibilityUtil } from "../libs/endorserServer";
import {
CONTACT_CSV_HEADER,
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
generateEndorserJwtUrlForAccount,
setVisibilityUtil,
} from "../libs/endorserServer";
import UserNameDialog from "../components/UserNameDialog.vue";
import { generateEndorserJwtUrlForAccount } from "../libs/endorserServer";
import { retrieveAccountMetadata } from "../libs/util";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { parseJsonField } from "../db/databaseUtil";
import { Account } from "@/db/tables/accounts";
interface QRScanResult {
rawValue?: string;
@@ -142,7 +149,7 @@ interface IUserNameDialog {
UserNameDialog,
},
})
export default class ContactQRScan extends Vue {
export default class ContactQRScanFull extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
$router!: Router;
@@ -151,6 +158,8 @@ export default class ContactQRScan extends Vue {
activeDid = "";
apiServer = "";
givenName = "";
isRegistered = false;
profileImageUrl = "";
qrValue = "";
ETHR_DID_PREFIX = ETHR_DID_PREFIX;
@@ -172,19 +181,22 @@ export default class ContactQRScan extends Vue {
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.givenName = settings.firstName || "";
this.isRegistered = !!settings.isRegistered;
this.profileImageUrl = settings.profileImageUrl || "";
const account = await retrieveAccountMetadata(this.activeDid);
if (account) {
const name =
(settings.firstName || "") +
(settings.lastName ? ` ${settings.lastName}` : "");
this.qrValue = await generateEndorserJwtUrlForAccount(
account,
!!settings.isRegistered,
name,
settings.profileImageUrl || "",
false,
);
const publicKeyBase64 = Buffer.from(
account.publicKeyHex,
"hex",
).toString("base64");
this.qrValue =
CONTACT_CSV_HEADER +
"\n" +
`"${name}",${account.did},${publicKeyBase64},false,${this.isRegistered}`;
}
} catch (error) {
logger.error("Error initializing component:", {
@@ -336,57 +348,69 @@ export default class ContactQRScan extends Vue {
logger.info("Processing QR code scan result:", rawValue);
// Extract JWT
const jwt = getContactJwtFromJwtUrl(rawValue);
if (!jwt) {
logger.warn("Invalid QR code format - no JWT found in URL");
let contact: Contact;
if (rawValue.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI)) {
// Extract JWT
const jwt = getContactJwtFromJwtUrl(rawValue);
if (!jwt) {
logger.warn("Invalid QR code format - no JWT found in URL");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid QR Code",
text: "This QR code does not contain valid contact information. Scan a TimeSafari contact QR code.",
});
return;
}
// Process JWT and contact info
logger.info("Decoding JWT payload from QR code");
const decodedJwt = await decodeEndorserJwt(jwt);
if (!decodedJwt?.payload?.own) {
logger.warn("Invalid JWT payload - missing 'own' field");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact Info",
text: "The contact information is incomplete or invalid.",
});
return;
}
const contactInfo = decodedJwt.payload.own;
const did = contactInfo.did || decodedJwt.payload.iss;
if (!did) {
logger.warn("Invalid contact info - missing DID");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact",
text: "The contact DID is missing.",
});
return;
}
// Create contact object
contact = {
did: did,
name: contactInfo.name || "",
publicKeyBase64: contactInfo.publicKeyBase64 || "",
seesMe: contactInfo.seesMe || false,
registered: contactInfo.registered || false,
};
} else if (rawValue.startsWith(CONTACT_CSV_HEADER)) {
const lines = rawValue.split(/\n/);
contact = libsUtil.csvLineToContact(lines[1]);
} else {
this.$notify({
group: "alert",
type: "danger",
title: "Invalid QR Code",
text: "This QR code does not contain valid contact information. Please scan a TimeSafari contact QR code.",
title: "Error",
text: "Could not determine the type of contact info. Try again, or tap the QR code to copy it and send it to them.",
});
return;
}
// Process JWT and contact info
logger.info("Decoding JWT payload from QR code");
const decodedJwt = await decodeEndorserJwt(jwt);
if (!decodedJwt?.payload?.own) {
logger.warn("Invalid JWT payload - missing 'own' field");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact Info",
text: "The contact information is incomplete or invalid.",
});
return;
}
const contactInfo = decodedJwt.payload.own;
const did = contactInfo.did || decodedJwt.payload.iss;
if (!did) {
logger.warn("Invalid contact info - missing DID");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact",
text: "The contact DID is missing.",
});
return;
}
// Create contact object
const contact = {
did: did,
name: contactInfo.name || "",
email: contactInfo.email || "",
phone: contactInfo.phone || "",
company: contactInfo.company || "",
title: contactInfo.title || "",
notes: contactInfo.notes || "",
};
// Add contact but keep scanning
logger.info("Adding new contact to database:", {
did: contact.did,
@@ -468,7 +492,7 @@ export default class ContactQRScan extends Vue {
title: "Contact Exists",
text: "This contact has already been added to your list.",
},
3000,
5000,
);
return;
}
@@ -568,9 +592,19 @@ export default class ContactQRScan extends Vue {
);
}
onCopyUrlToClipboard() {
async onCopyUrlToClipboard() {
const account = (await libsUtil.retrieveFullyDecryptedAccount(
this.activeDid,
)) as Account;
const jwtUrl = await generateEndorserJwtUrlForAccount(
account,
this.isRegistered,
this.givenName,
this.profileImageUrl,
true,
);
useClipboard()
.copy(this.qrValue)
.copy(jwtUrl)
.then(() => {
this.$notify(
{

View File

@@ -159,6 +159,7 @@
<script lang="ts">
import { AxiosError } from "axios";
import { Buffer } from "buffer/";
import QRCodeVue3 from "qr-code-generator-vue3";
import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core";
@@ -174,17 +175,20 @@ import * as databaseUtil from "../db/databaseUtil";
import { parseJsonField } from "../db/databaseUtil";
import { getContactJwtFromJwtUrl } from "../libs/crypto";
import {
CONTACT_CSV_HEADER,
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
generateEndorserJwtUrlForAccount,
register,
setVisibilityUtil,
} from "../libs/endorserServer";
import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc";
import { retrieveAccountMetadata } from "../libs/util";
import * as libsUtil from "../libs/util";
import { Router } from "vue-router";
import { logger } from "../utils/logger";
import { QRScannerFactory } from "@/services/QRScanner/QRScannerFactory";
import { CameraState } from "@/services/QRScanner/types";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { Account } from "@/db/tables/accounts";
interface QRScanResult {
rawValue?: string;
@@ -214,6 +218,7 @@ export default class ContactQRScanShow extends Vue {
isRegistered = false;
qrValue = "";
isScanning = false;
profileImageUrl = "";
error: string | null = null;
// QR Scanner properties
@@ -251,19 +256,21 @@ export default class ContactQRScanShow extends Vue {
this.hideRegisterPromptOnNewContact =
!!settings.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings.isRegistered;
this.profileImageUrl = settings.profileImageUrl || "";
const account = await retrieveAccountMetadata(this.activeDid);
const account = await libsUtil.retrieveAccountMetadata(this.activeDid);
if (account) {
const name =
(settings.firstName || "") +
(settings.lastName ? ` ${settings.lastName}` : "");
this.qrValue = await generateEndorserJwtUrlForAccount(
account,
!!settings.isRegistered,
name,
settings.profileImageUrl || "",
false,
);
const publicKeyBase64 = Buffer.from(
account.publicKeyHex,
"hex",
).toString("base64");
this.qrValue =
CONTACT_CSV_HEADER +
"\n" +
`"${name}",${account.did},${publicKeyBase64},false,${this.isRegistered}`;
}
} catch (error) {
logger.error("Error initializing component:", {
@@ -274,7 +281,7 @@ export default class ContactQRScanShow extends Vue {
group: "alert",
type: "danger",
title: "Initialization Error",
text: "Failed to initialize QR scanner. Please try again.",
text: "Failed to initialize QR renderer or scanner. Please try again.",
});
}
}
@@ -461,53 +468,68 @@ export default class ContactQRScanShow extends Vue {
logger.info("Processing QR code scan result:", rawValue);
// Extract JWT
const jwt = getContactJwtFromJwtUrl(rawValue);
if (!jwt) {
logger.warn("Invalid QR code format - no JWT found in URL");
let contact: Contact;
if (rawValue.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI)) {
const jwt = getContactJwtFromJwtUrl(rawValue);
if (!jwt) {
logger.warn("Invalid QR code format - no JWT found in URL");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid QR Code",
text: "This QR code does not contain valid contact information. Scan a TimeSafari contact QR code.",
});
return;
}
logger.info("Decoding JWT payload from QR code");
const decodedJwt = await decodeEndorserJwt(jwt);
// Process JWT and contact info
if (!decodedJwt?.payload?.own) {
logger.warn("Invalid JWT payload - missing 'own' field");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact Info",
text: "The contact information is incomplete or invalid.",
});
return;
}
const contactInfo = decodedJwt.payload.own;
const did = contactInfo.did || decodedJwt.payload.iss;
if (!did) {
logger.warn("Invalid contact info - missing DID");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact",
text: "The contact DID is missing.",
});
return;
}
// Create contact object
contact = {
did: did,
name: contactInfo.name || "",
publicKeyBase64: contactInfo.publicKeyBase64 || "",
seesMe: contactInfo.seesMe || false,
registered: contactInfo.registered || false,
};
} else if (rawValue.startsWith(CONTACT_CSV_HEADER)) {
const lines = rawValue.split(/\n/);
contact = libsUtil.csvLineToContact(lines[1]);
} else {
this.$notify({
group: "alert",
type: "danger",
title: "Invalid QR Code",
text: "This QR code does not contain valid contact information. Please scan a TimeSafari contact QR code.",
title: "Error",
text: "Could not determine the type of contact info. Try again, or tap the QR code to copy it and send it to them.",
});
return;
}
// Process JWT and contact info
logger.info("Decoding JWT payload from QR code");
const decodedJwt = await decodeEndorserJwt(jwt);
if (!decodedJwt?.payload?.own) {
logger.warn("Invalid JWT payload - missing 'own' field");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact Info",
text: "The contact information is incomplete or invalid.",
});
return;
}
const contactInfo = decodedJwt.payload.own;
const did = contactInfo.did || decodedJwt.payload.iss;
if (!did) {
logger.warn("Invalid contact info - missing DID");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact",
text: "The contact DID is missing.",
});
return;
}
// Create contact object
const contact = {
did: did,
name: contactInfo.name || "",
notes: contactInfo.notes || "",
};
// Add contact but keep scanning
logger.info("Adding new contact to database:", {
did: contact.did,
@@ -649,12 +671,20 @@ export default class ContactQRScanShow extends Vue {
});
}
onCopyUrlToClipboard() {
//this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing
async onCopyUrlToClipboard() {
const account = (await libsUtil.retrieveFullyDecryptedAccount(
this.activeDid,
)) as Account;
const jwtUrl = await generateEndorserJwtUrlForAccount(
account,
this.isRegistered,
this.givenName,
this.profileImageUrl,
true,
);
useClipboard()
.copy(this.qrValue)
.copy(jwtUrl)
.then(() => {
// console.log("Contact URL:", this.qrValue);
this.$notify(
{
group: "alert",
@@ -772,7 +802,7 @@ export default class ContactQRScanShow extends Vue {
title: "Contact Exists",
text: "This contact has already been added to your list.",
},
3000,
5000,
);
return;
}

View File

@@ -126,7 +126,6 @@
<div class="flex items-center gap-2">
<button
v-if="showGiveNumbers"
href=""
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
:class="showGiveAmountsClassNames()"
@click="toggleShowGiveTotals()"
@@ -142,7 +141,6 @@
</button>
<button
href=""
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
@click="toggleShowContactAmounts()"
>
@@ -493,7 +491,7 @@ export default class ContactsView extends Vue {
private async processContactJwt() {
// handle a contact sent via URL
//
// For external links, use /contact-import/:jwt with a JWT that has an array of contacts
// For external links, use /deep-link/contact-import/:jwt with a JWT that has an array of contacts
// because that will do better error checking for things like missing data on iOS platforms.
const importedContactJwt = this.$route.query["contactJwt"] as string;
if (importedContactJwt) {
@@ -619,7 +617,7 @@ export default class ContactsView extends Vue {
title: "Error with Invite",
text: message,
},
5000,
-1,
);
}
// if we're here, they haven't redirected anywhere, so we'll redirect here without a query parameter
@@ -935,45 +933,9 @@ export default class ContactsView extends Vue {
}
private async addContactFromEndorserMobileLine(
line: string,
lineRaw: string,
): Promise<IndexableType> {
// Note that Endorser Mobile puts name first, then did, etc.
let name = line;
let did = "";
let publicKeyInput, seesMe, registered;
const commaPos1 = line.indexOf(",");
if (commaPos1 > -1) {
name = line.substring(0, commaPos1).trim();
did = line.substring(commaPos1 + 1).trim();
const commaPos2 = line.indexOf(",", commaPos1 + 1);
if (commaPos2 > -1) {
did = line.substring(commaPos1 + 1, commaPos2).trim();
publicKeyInput = line.substring(commaPos2 + 1).trim();
const commaPos3 = line.indexOf(",", commaPos2 + 1);
if (commaPos3 > -1) {
publicKeyInput = line.substring(commaPos2 + 1, commaPos3).trim();
seesMe = line.substring(commaPos3 + 1).trim() == "true";
const commaPos4 = line.indexOf(",", commaPos3 + 1);
if (commaPos4 > -1) {
seesMe = line.substring(commaPos3 + 1, commaPos4).trim() == "true";
registered = line.substring(commaPos4 + 1).trim() == "true";
}
}
}
}
// help with potential mistakes while this sharing requires copy-and-paste
let publicKeyBase64 = publicKeyInput;
if (publicKeyBase64 && /^[0-9A-Fa-f]{66}$/i.test(publicKeyBase64)) {
// it must be all hex (compressed public key), so convert
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
}
const newContact = {
did,
name,
publicKeyBase64,
seesMe,
registered,
};
const newContact = libsUtil.csvLineToContact(lineRaw);
const platformService = PlatformServiceFactory.getInstance();
const { sql, params } = databaseUtil.generateInsertStatement(
newContact as unknown as Record<string, unknown>,
@@ -1160,7 +1122,7 @@ export default class ContactsView extends Vue {
(regResult.error as string) ||
"Something went wrong during registration.",
},
5000,
-1,
);
}
} catch (error) {
@@ -1194,7 +1156,7 @@ export default class ContactsView extends Vue {
title: "Registration Error",
text: userMessage,
},
5000,
-1,
);
}
}
@@ -1215,7 +1177,6 @@ export default class ContactsView extends Vue {
);
if (result.success) {
//contact.seesMe = visibility; // why doesn't it affect the UI from here?
//console.log("Set result & seesMe", result, contact.seesMe, contact.did);
if (showSuccessAlert) {
this.$notify(
{
@@ -1431,14 +1392,11 @@ export default class ContactsView extends Vue {
}
return contact;
});
// console.log(
// "Array of selected contacts:",
// JSON.stringify(selectedContacts),
// );
const contactsJwt = await createEndorserJwtForDid(this.activeDid, {
contacts: selectedContacts,
});
const contactsJwtUrl = APP_SERVER + "/contact-import/" + contactsJwt;
const contactsJwtUrl =
APP_SERVER + "/deep-link/contact-import/" + contactsJwt;
useClipboard()
.copy(contactsJwtUrl)
.then(() => {

View File

@@ -66,9 +66,14 @@ const formattedPath = computed(() => {
const path = originalPath.value.replace(/^\/+/, "");
// Log for debugging
logger.log("Original Path:", originalPath.value);
logger.log("Route Params:", route.params);
logger.log("Route Query:", route.query);
logger.log(
"[DeepLinkError] Original Path:",
originalPath.value,
"Route Params:",
route.params,
"Route Query:",
route.query,
);
return path;
});

View File

@@ -0,0 +1,227 @@
<template>
<!-- CONTENT -->
<section id="Content" class="relative w-[100vw] h-[100vh]">
<div
class="p-6 bg-white w-full max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto"
>
<div class="mb-4">
<h1 class="text-xl text-center font-semibold relative mb-4">
Redirecting to Time Safari
</h1>
<div v-if="destinationUrl" class="space-y-4">
<!-- Platform-specific messaging -->
<div class="text-center text-gray-600 mb-4">
<p v-if="isMobile">
{{
isIOS
? "Opening Time Safari app on your iPhone..."
: "Opening Time Safari app on your Android device..."
}}
</p>
<p v-else>Opening Time Safari app...</p>
<p class="text-sm mt-2">
<span v-if="isMobile"
>If the app doesn't open automatically, use one of these
options:</span
>
<span v-else>Choose how you'd like to open this link:</span>
</p>
</div>
<!-- Deep Link Button -->
<div class="text-center">
<a
:href="deepLinkUrl || '#'"
class="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors"
@click="handleDeepLinkClick"
>
<span v-if="isMobile">Open in Time Safari App</span>
<span v-else>Try Opening in Time Safari App</span>
</a>
</div>
<!-- Web Fallback Link -->
<div class="text-center">
<a
:href="webUrl || '#'"
target="_blank"
class="inline-block bg-gray-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-gray-700 transition-colors"
@click="handleWebFallbackClick"
>
<span v-if="isMobile">Open in Web Browser Instead</span>
<span v-else>Open in Web Browser</span>
</a>
</div>
<!-- Manual Instructions -->
<div class="text-center text-sm text-gray-500 mt-4">
<p v-if="isMobile">
Or manually open:
<code class="bg-gray-100 px-2 py-1 rounded">{{
deepLinkUrl
}}</code>
</p>
<p v-else>
If you have the Time Safari app installed, you can also copy this
link:
<code class="bg-gray-100 px-2 py-1 rounded">{{
deepLinkUrl
}}</code>
</p>
</div>
<!-- Platform info for debugging -->
<div
v-if="isDevelopment"
class="text-center text-xs text-gray-400 mt-4"
>
<p>
Platform: {{ isMobile ? (isIOS ? "iOS" : "Android") : "Desktop" }}
</p>
<p>User Agent: {{ userAgent.substring(0, 50) }}...</p>
</div>
</div>
<div v-else-if="pageError" class="text-center text-red-500 mb-4">
{{ pageError }}
</div>
<div v-else class="text-center text-gray-600">
<p>Processing redirect...</p>
</div>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import { APP_SERVER } from "@/constants/app";
import { logger } from "@/utils/logger";
import { errorStringForLog } from "@/libs/endorserServer";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
@Component({})
export default class DeepLinkRedirectView extends Vue {
$router!: Router;
$route!: RouteLocationNormalizedLoaded;
pageError: string | null = null;
destinationUrl: string | null = null; // full path after "/deep-link/"
deepLinkUrl: string | null = null; // mobile link starting "timesafari://"
webUrl: string | null = null; // web link, eg "https://timesafari.app/..."
isDevelopment: boolean = false;
userAgent: string = "";
private platformService = PlatformServiceFactory.getInstance();
mounted() {
// Get the path from the route parameter (catch-all parameter)
const pathParam = this.$route.params.path;
// If pathParam is an array (catch-all parameter), join it
const fullPath = Array.isArray(pathParam) ? pathParam.join("/") : pathParam;
// Get query parameters from the route
const queryParams = this.$route.query;
// Build query string if there are query parameters
let queryString = "";
if (Object.keys(queryParams).length > 0) {
const searchParams = new URLSearchParams();
Object.entries(queryParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
const stringValue = Array.isArray(value) ? value[0] : value;
if (stringValue !== null && stringValue !== undefined) {
searchParams.append(key, stringValue);
}
}
});
queryString = "?" + searchParams.toString();
}
// Combine path with query parameters
const fullPathWithQuery = fullPath + queryString;
this.destinationUrl = fullPathWithQuery;
this.deepLinkUrl = `timesafari://${fullPathWithQuery}`;
this.webUrl = `${APP_SERVER}/${fullPathWithQuery}`;
this.isDevelopment = process.env.NODE_ENV !== "production";
this.userAgent = navigator.userAgent;
this.openDeepLink();
}
private openDeepLink() {
if (!this.deepLinkUrl || !this.webUrl) {
this.pageError =
"No deep link was provided. Check the URL and try again.";
return;
}
try {
// For mobile, try the deep link URL; for desktop, use the web URL
const redirectUrl = this.isMobile ? this.deepLinkUrl : this.webUrl;
// Method 1: Try window.location.href (works on most browsers)
window.location.href = redirectUrl;
// Method 2: Fallback - create and click a link element
setTimeout(() => {
try {
const link = document.createElement("a");
link.href = redirectUrl;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
logger.error(
"Fallback deep link failed: " + errorStringForLog(error),
);
this.pageError =
"Redirecting to the Time Safari app failed. Please use a manual option below.";
}
}, 100);
} catch (error) {
logger.error("Deep link redirect failed: " + errorStringForLog(error));
this.pageError =
"Unable to open the Time Safari app. Please use a manual option below.";
}
}
private handleDeepLinkClick(event: Event) {
if (!this.deepLinkUrl) return;
// Prevent default to handle the click manually
event.preventDefault();
this.openDeepLink();
}
private handleWebFallbackClick(event: Event) {
if (!this.webUrl) return;
// Get platform capabilities
const capabilities = this.platformService.getCapabilities();
// For mobile, try to open in a new tab/window
if (capabilities.isMobile) {
event.preventDefault();
window.open(this.webUrl, "_blank");
}
// For desktop, let the default behavior happen (opens in same tab)
}
// Computed properties for template
get isMobile(): boolean {
return this.platformService.getCapabilities().isMobile;
}
get isIOS(): boolean {
return this.platformService.getCapabilities().isIOS;
}
}
</script>

View File

@@ -523,9 +523,7 @@ export default class DiscoverView extends Vue {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
logger.error("Error with search all:", e);
// this sometimes gives different information
logger.error("Error with search all (error added): " + e);
logger.error("Error with search all: " + errorStringForLog(e));
this.$notify(
{
group: "alert",
@@ -617,7 +615,7 @@ export default class DiscoverView extends Vue {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
logger.error("Error with search local:", e);
logger.error("Error with search local: " + errorStringForLog(e));
this.$notify(
{
group: "alert",
@@ -788,7 +786,7 @@ export default class DiscoverView extends Vue {
const route = {
path: this.isProjectsActive
? "/project/" + encodeURIComponent(id)
: "/userProfile/" + encodeURIComponent(id),
: "/user-profile/" + encodeURIComponent(id),
};
this.$router.push(route);
}

View File

@@ -826,7 +826,7 @@ export default class GiftedDetails extends Vue {
}
if (!result.success) {
const errorMessage = this.getGiveCreationErrorMessage(result);
const errorMessage = result.error;
logger.error("Error with give creation result:", result);
this.$notify(
{
@@ -899,19 +899,6 @@ export default class GiftedDetails extends Vue {
// Helper functions for readability
/**
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
* @returns best guess at an error message
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getGiveCreationErrorMessage(result: any) {
return (
result.error?.userMessage ||
result.error?.error ||
result.response?.data?.error?.message
);
}
explainData() {
this.$notify(
{

View File

@@ -24,11 +24,11 @@
<!-- eslint-disable prettier/prettier max-len -->
<div>
<p>
This app focuses on gifts & gratitude, using them to build cool things together with your network.
This app focuses on raw gratitude, using it to build cool things together with your network.
</p>
<p class="ml-4">
If you'd like to see the page-by-page help,
If you'd like to see the page-by-page help again,
<span
class="text-blue-500 cursor-pointer"
@click="unsetFinishedOnboarding()"
@@ -37,14 +37,16 @@
<h2 class="text-xl font-semibold">What is the idea here?</h2>
<p>
We are building networks of people who want to grow good society from the ground up, using modern
technology that connects people peer-to-peer.
First of all, let's showcase gratitude: see what people have given, and recognize
gifts you've seen. This is done in a way that leaves a permanent record -- one that
came from you, and one that the recipient can prove it was for them. This can be
personally gratifying, but it extends to broader work: volunteers get
confirmation of activity, and they can selectively show off their contributions
and network.
We are building networks of people who want to grow good society from the ground up, using
modern technology that connects people peer-to-peer.
First of all, let's showcase gratitude: see what people have given, and recognize gifts
you've seen. This is done in a way that leaves a permanent record -- one that provably
came from you, and one that the recipient can prove they were mentioned.
This can be personally gratifying, but it extends to broader work: volunteers get
confirmation of activity, and they can selectively show off their contributions and
network.
This is a way to build trust and reputation. It's a way to build a network of people who
are willing to help each other.
</p>
<p class="mt-2">
With this, you highlight giving and you also offer help --
@@ -555,9 +557,6 @@
initiative.
</p>
<h2 class="text-xl font-semibold">What app version is this?</h2>
<p>{{ package.version }} ({{ commitHash }})</p>
<h2 class="text-xl font-semibold">
I have other questions or feedback, like getting a new profile or removing my data or requesting an improvement.
</h2>
@@ -567,6 +566,28 @@
>info@TimeSafari.app</a
>
</p>
<h2 class="text-xl font-semibold">What app version is this?</h2>
<p>{{ package.version }} ({{ commitHash }})</p>
<div v-if="Capacitor.isNativePlatform()">
<h2 class="text-xl font-semibold">
Do I have the latest version?
</h2>
<p v-if="Capacitor.getPlatform() === 'ios'">
<a href="https://apps.apple.com/us/app/time-safari/id6742664907" target="_blank" class="text-blue-500">
Check the App Store.
</a>
</p>
<p v-else-if="Capacitor.getPlatform() === 'android'">
<a href="https://timesafari.app/app.apk" target="_blank" class="text-blue-500">
Download the latest APK to see.
</a>
</p>
<p v-else>
Sorry, your platform of '{{ Capacitor.getPlatform() }}' is not recognized.
</p>
</div>
</div>
<!-- eslint enable -->
</section>
@@ -603,6 +624,7 @@ export default class HelpView extends Vue {
showVerifiable = false;
APP_SERVER = APP_SERVER;
Capacitor = Capacitor;
// Ideally, we put no functionality in here, especially in the setup,
// because we never want this page to have a chance of throwing an error.

View File

@@ -519,7 +519,6 @@ export default class HomeView extends Vue {
// Retrieve DIDs with better error handling
try {
this.allMyDids = await retrieveAccountDids();
logConsoleAndDb(`[HomeView] Retrieved ${this.allMyDids.length} DIDs`);
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to retrieve DIDs: ${error}`, true);
throw new Error(
@@ -552,9 +551,6 @@ export default class HomeView extends Vue {
if (USE_DEXIE_DB) {
settings = await retrieveSettingsForActiveAccount();
}
logConsoleAndDb(
`[HomeView] Retrieved settings for ${settings.activeDid || "no active DID"}`,
);
} catch (error) {
logConsoleAndDb(
`[HomeView] Failed to retrieve settings: ${error}`,
@@ -581,9 +577,6 @@ export default class HomeView extends Vue {
if (USE_DEXIE_DB) {
this.allContacts = await db.contacts.toArray();
}
logConsoleAndDb(
`[HomeView] Retrieved ${this.allContacts.length} contacts`,
);
} catch (error) {
logConsoleAndDb(
`[HomeView] Failed to retrieve contacts: ${error}`,
@@ -641,9 +634,6 @@ export default class HomeView extends Vue {
});
}
this.isRegistered = true;
logConsoleAndDb(
`[HomeView] User ${this.activeDid} is now registered`,
);
}
} catch (error) {
logConsoleAndDb(
@@ -685,11 +675,6 @@ export default class HomeView extends Vue {
this.newOffersToUserHitLimit = offersToUser.hitLimit;
this.numNewOffersToUserProjects = offersToProjects.data.length;
this.newOffersToUserProjectsHitLimit = offersToProjects.hitLimit;
logConsoleAndDb(
`[HomeView] Retrieved ${this.numNewOffersToUser} user offers and ` +
`${this.numNewOffersToUserProjects} project offers`,
);
}
} catch (error) {
logConsoleAndDb(
@@ -1843,7 +1828,7 @@ export default class HomeView extends Vue {
this.axios,
);
if (result.type === "success") {
if (result.success) {
this.$notify(
{
group: "alert",

View File

@@ -83,7 +83,7 @@
<span
v-else
class="text-center text-slate-500 cursor-pointer"
:title="inviteLink(invite.jwt)"
:title="invite.inviteIdentifier"
@click="
showInvite(
invite.inviteIdentifier,
@@ -241,7 +241,7 @@ export default class InviteOneView extends Vue {
}
inviteLink(jwt: string): string {
return APP_SERVER + "/invite-one-accept/" + jwt;
return APP_SERVER + "/deep-link/invite-one-accept/" + jwt;
}
copyInviteAndNotify(inviteId: string, jwt: string) {
@@ -324,7 +324,7 @@ export default class InviteOneView extends Vue {
);
await axios.post(
this.apiServer + "/api/userUtil/invite",
{ inviteIdentifier, inviteJwt, notes, expiresAt },
{ inviteJwt, notes, expiresAt },
{ headers },
);
const newInvite = {

View File

@@ -720,7 +720,7 @@ export default class OnboardMeetingView extends Vue {
onboardMeetingMembersLink(): string {
if (this.currentMeeting) {
return `${APP_SERVER}/onboard-meeting-members/${this.currentMeeting?.groupId}?password=${encodeURIComponent(
return `${APP_SERVER}/deep-link/onboard-meeting-members/${this.currentMeeting?.groupId}?password=${encodeURIComponent(
this.currentMeeting?.password || "",
)}`;
}

View File

@@ -27,6 +27,12 @@
>
<font-awesome icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
</button>
<button title="Copy Link to Project" @click="onCopyLinkClick()">
<font-awesome
icon="link"
class="text-sm text-slate-500 ml-2 mb-1"
/>
</button>
</h2>
</div>
</div>
@@ -52,16 +58,28 @@
icon="user"
class="fa-fw text-slate-400"
></font-awesome>
{{ issuerInfoObject?.displayName }}
<span v-if="!serverUtil.isEmptyOrHiddenDid(issuer)">
<a :href="`/did/${issuer}`" class="text-blue-500">
<span class="truncate inline-block max-w-[calc(100%-2rem)]">
{{ issuerInfoObject?.displayName }}
</span>
<span
v-if="!serverUtil.isHiddenDid(issuer)"
class="inline-flex items-center"
>
<router-link
:to="{
path: '/did/' + encodeURIComponent(issuer),
}"
class="text-blue-500 ml-1"
title="See more about this person"
>
<font-awesome
icon="arrow-up-right-from-square"
class="fa-fw"
/>
</a>
</router-link>
</span>
<span v-else-if="serverUtil.isHiddenDid(issuer)">
<span v-if="serverUtil.isHiddenDid(issuer)" class="ml-1">
<font-awesome
icon="info-circle"
class="fa-fw text-blue-500 cursor-pointer"
@@ -105,7 +123,7 @@
class="fa-fw text-slate-400"
></font-awesome>
<a
:href="addScheme(url)"
:href="ensureScheme(url)"
target="_blank"
class="underline text-blue-500"
>
@@ -624,7 +642,7 @@ import TopMessage from "../components/TopMessage.vue";
import QuickNav from "../components/QuickNav.vue";
import EntityIcon from "../components/EntityIcon.vue";
import ProjectIcon from "../components/ProjectIcon.vue";
import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
import { APP_SERVER, NotificationIface, USE_DEXIE_DB } from "../constants/app";
import * as databaseUtil from "../db/databaseUtil";
import {
db,
@@ -638,6 +656,7 @@ import { retrieveAccountDids } from "../libs/util";
import HiddenDidDialog from "../components/HiddenDidDialog.vue";
import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { useClipboard } from "@vueuse/core";
/**
* Project View Component
* @author Matthew Raymer
@@ -834,6 +853,28 @@ export default class ProjectViewView extends Vue {
});
}
onCopyLinkClick() {
const shortestProjectId = this.projectId.startsWith(
serverUtil.ENDORSER_CH_HANDLE_PREFIX,
)
? this.projectId.substring(serverUtil.ENDORSER_CH_HANDLE_PREFIX.length)
: this.projectId;
const deepLink = `${APP_SERVER}/deep-link/project/${shortestProjectId}`;
useClipboard()
.copy(deepLink)
.then(() => {
this.$notify(
{
group: "alert",
type: "toast",
title: "Copied",
text: "A link to this project was copied to the clipboard.",
},
2000,
);
});
}
// Isn't there a better way to make this available to the template?
expandText() {
this.expanded = true;
@@ -1296,7 +1337,7 @@ export default class ProjectViewView extends Vue {
}
// return an HTTPS URL if it's not a global URL
addScheme(url: string) {
ensureScheme(url: string) {
if (!libsUtil.isGlobalUri(url)) {
return "https://" + url;
}
@@ -1425,7 +1466,7 @@ export default class ProjectViewView extends Vue {
this.apiServer,
this.axios,
);
if (result.type === "success") {
if (result.success) {
this.$notify(
{
group: "alert",
@@ -1457,7 +1498,13 @@ export default class ProjectViewView extends Vue {
}
openHiddenDidDialog() {
const shortestProjectId = this.projectId.startsWith(
serverUtil.ENDORSER_CH_HANDLE_PREFIX,
)
? this.projectId.substring(serverUtil.ENDORSER_CH_HANDLE_PREFIX.length)
: this.projectId;
(this.$refs.hiddenDidDialog as HiddenDidDialog).open(
"project/" + shortestProjectId,
"creator",
this.issuerVisibleToDids,
this.allContacts,

View File

@@ -155,7 +155,7 @@ import { Contact } from "../db/tables/contacts";
import {
GenericCredWrapper,
GenericVerifiableCredential,
ErrorResult,
CreateAndSubmitClaimResult,
} from "../interfaces";
import {
BVC_MEETUPS_PROJECT_CLAIM_ID,
@@ -298,28 +298,29 @@ export default class QuickActionBvcBeginView extends Vue {
}
// in parallel, make a confirmation for each selected claim and send them all to the server
const confirmResults = await Promise.allSettled(
this.claimsToConfirmSelected.map(async (jwtId) => {
const record = this.claimsToConfirm.find(
(claim) => claim.id === jwtId,
);
if (!record) {
return { type: "error", error: "Record not found." };
}
return createAndSubmitConfirmation(
this.activeDid,
record.claim as GenericVerifiableCredential,
record.id,
record.handleId,
this.apiServer,
axios,
);
}),
);
const confirmResults: PromiseSettledResult<CreateAndSubmitClaimResult>[] =
await Promise.allSettled(
this.claimsToConfirmSelected.map(async (jwtId) => {
const record = this.claimsToConfirm.find(
(claim) => claim.id === jwtId,
);
if (!record) {
return { success: false, error: "Record not found." };
}
return createAndSubmitConfirmation(
this.activeDid,
record.claim as GenericVerifiableCredential,
record.id,
record.handleId,
this.apiServer,
axios,
);
}),
);
// check for any rejected confirmations
const confirmsSucceeded = confirmResults.filter(
(result) =>
result.status === "fulfilled" && result.value.type === "success",
// 'fulfilled' is the status in a successful PromiseFulfilledResult
(result) => result.status === "fulfilled" && result.value.success,
);
if (confirmsSucceeded.length < this.claimsToConfirmSelected.length) {
logger.error("Error sending confirmations:", confirmResults);
@@ -353,7 +354,7 @@ export default class QuickActionBvcBeginView extends Vue {
undefined,
BVC_MEETUPS_PROJECT_CLAIM_ID,
);
giveSucceeded = giveResult.type === "success";
giveSucceeded = giveResult.success;
if (!giveSucceeded) {
logger.error("Error sending give:", giveResult);
this.$notify(
@@ -362,7 +363,7 @@ export default class QuickActionBvcBeginView extends Vue {
type: "danger",
title: "Error",
text:
(giveResult as ErrorResult)?.error?.userMessage ||
(giveResult as CreateAndSubmitClaimResult)?.error ||
"There was an error sending that give.",
},
5000,

View File

@@ -105,7 +105,7 @@ export default class ShareMyContactInfoView extends Vue {
group: "alert",
type: "info",
title: "Copied",
text: "Your contact info was copied to the clipboard. Have them paste it in the box on their 'Contacts' screen.",
text: "Your contact info was copied to the clipboard. Have them click on it, or paste it in the box on their 'Contacts' screen.",
},
5000,
);

View File

@@ -16,6 +16,7 @@
</button>
Individual Profile
</h1>
<div class="text-sm text-center text-slate-500"></div>
</div>
<!-- Loading Animation -->
@@ -32,6 +33,12 @@
<div class="text-sm">
<font-awesome icon="user" class="fa-fw text-slate-400"></font-awesome>
{{ didInfo(profile.issuerDid, activeDid, allMyDids, allContacts) }}
<button title="Copy Link to Profile" @click="onCopyLinkClick()">
<font-awesome
icon="link"
class="text-sm text-slate-500 ml-2 mb-1"
/>
</button>
</div>
<p v-if="profile.description" class="mt-4 text-slate-600">
{{ profile.description }}
@@ -100,6 +107,7 @@ import { Router, RouteLocationNormalizedLoaded } from "vue-router";
import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue";
import {
APP_SERVER,
DEFAULT_PARTNER_API_SERVER,
NotificationIface,
USE_DEXIE_DB,
@@ -113,6 +121,7 @@ import { retrieveAccountDids } from "../libs/util";
import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { Settings } from "@/db/tables/settings";
import { useClipboard } from "@vueuse/core";
@Component({
components: {
LMap,
@@ -186,6 +195,10 @@ export default class UserProfileView extends Vue {
if (response.status === 200) {
const result = await response.json();
this.profile = result.data;
if (this.profile && this.profile.rowId !== profileId) {
// currently the server returns "rowid" with lowercase "i"; remove when that's fixed
this.profile.rowId = profileId;
}
} else {
throw new Error("Failed to load profile");
}
@@ -204,5 +217,22 @@ export default class UserProfileView extends Vue {
this.isLoading = false;
}
}
onCopyLinkClick() {
const deepLink = `${APP_SERVER}/deep-link/user-profile/${this.profile?.rowId}`;
useClipboard()
.copy(deepLink)
.then(() => {
this.$notify(
{
group: "alert",
type: "toast",
title: "Copied",
text: "A link to this profile was copied to the clipboard.",
},
2000,
);
});
}
}
</script>