Compare commits

...

17 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
36 changed files with 1137 additions and 578 deletions

View File

@@ -41,6 +41,7 @@ Install dependencies:
1. Run the production build: 1. Run the production build:
```bash ```bash
rm -rf dist
npm run build:web npm run build:web
``` ```
@@ -64,6 +65,8 @@ Install dependencies:
* Commit everything (since the commit hash is used the app). * 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). * 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`. * 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): * For test, build the app (because test server is not yet set up to build):
```bash ```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: ... and transfer to the test server:
@@ -334,25 +337,18 @@ Prerequisites: macOS with Xcode installed
export GEM_PATH=$shortened_path export GEM_PATH=$shortened_path
``` ```
1. Check the iOS flag isIOS in CapacitorPlatformService (currently hard-coded for iOS build). 1. Build the web assets & update ios:
2. Build the web assets:
```bash ```bash
rm -rf dist rm -rf dist
npm run build:web npm run build:web
npm run build:capacitor npm run build:capacitor
```
3. Update iOS project with latest build:
```bash
npx cap sync ios npx cap sync ios
``` ```
- If that fails with "Could not find..." then look at the "gem_path" instructions above. - If that fails with "Could not find..." then look at the "gem_path" instructions above.
4. Copy the assets: 3. Copy the assets:
```bash ```bash
# It makes no sense why capacitor-assets will not run without these but it actually changes the contents. # It makes no sense why capacitor-assets will not run without these but it actually changes the contents.
@@ -367,10 +363,10 @@ Prerequisites: macOS with Xcode installed
``` ```
cd ios/App cd ios/App
xcrun agvtool new-version 30 xcrun agvtool new-version 34
# Unfortunately this edits Info.plist directly. # Unfortunately this edits Info.plist directly.
#xcrun agvtool new-marketing-version 0.4.5 #xcrun agvtool new-marketing-version 0.4.5
cat App.xcodeproj/project.pbxproj | sed "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 0.5.4;/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 - cd -
``` ```
@@ -398,8 +394,6 @@ Prerequisites: macOS with Xcode installed
* You'll probably have to "Manage" something about encryption, disallowed in France. * You'll probably have to "Manage" something about encryption, disallowed in France.
* Then "Save" and "Add to Review" and "Resubmit to App Review". * Then "Save" and "Add to Review" and "Resubmit to App Review".
8. Revert the iOS flag isIOS in CapacitorPlatformService.
### Android Build ### Android Build
Prerequisites: Android Studio with Java SDK installed Prerequisites: Android Studio with Java SDK installed
@@ -441,7 +435,6 @@ Prerequisites: Android Studio with Java SDK installed
./gradlew clean ./gradlew clean
./gradlew build -Dlint.baselines.continue=true ./gradlew build -Dlint.baselines.continue=true
cd - cd -
npx cap run android
``` ```
... or, to create the `aab` file, `bundle` instead of `build`: ... or, to create the `aab` file, `bundle` instead of `build`:

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). 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] ## [0.4.7]
### Fixed ### Fixed

View File

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

View File

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

View File

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

View File

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

579
package-lock.json generated
View File

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

View File

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

View File

@@ -77,7 +77,7 @@
If you'd like an introduction, If you'd like an introduction,
<a <a
class="text-blue-500" 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 >click here to copy this page, paste it into a message, and ask if
they'll tell you more about the {{ roleName }}.</a they'll tell you more about the {{ roleName }}.</a
> >
@@ -104,7 +104,7 @@ import * as R from "ramda";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import * as serverUtil from "../libs/endorserServer"; import * as serverUtil from "../libs/endorserServer";
import { NotificationIface } from "../constants/app"; import { APP_SERVER, NotificationIface } from "../constants/app";
@Component @Component
export default class HiddenDidDialog extends Vue { export default class HiddenDidDialog extends Vue {
@@ -117,7 +117,8 @@ export default class HiddenDidDialog extends Vue {
activeDid = ""; activeDid = "";
allMyDids: Array<string> = []; allMyDids: Array<string> = [];
canShare = false; canShare = false;
windowLocation = window.location.href; deepLinkPathSuffix = "";
deepLinkUrl = window.location.href; // this is changed to a deep link in the setup
R = R; R = R;
serverUtil = serverUtil; serverUtil = serverUtil;
@@ -129,17 +130,21 @@ export default class HiddenDidDialog extends Vue {
} }
open( open(
deepLinkPathSuffix: string,
roleName: string, roleName: string,
visibleToDids: string[], visibleToDids: string[],
allContacts: Array<Contact>, allContacts: Array<Contact>,
activeDid: string, activeDid: string,
allMyDids: Array<string>, allMyDids: Array<string>,
) { ) {
this.deepLinkPathSuffix = deepLinkPathSuffix;
this.roleName = roleName; this.roleName = roleName;
this.visibleToDids = visibleToDids; this.visibleToDids = visibleToDids;
this.allContacts = allContacts; this.allContacts = allContacts;
this.activeDid = activeDid; this.activeDid = activeDid;
this.allMyDids = allMyDids; this.allMyDids = allMyDids;
this.deepLinkUrl = APP_SERVER + "/deep-link/" + this.deepLinkPathSuffix;
this.isOpen = true; this.isOpen = true;
} }
@@ -173,11 +178,11 @@ export default class HiddenDidDialog extends Vue {
} }
onClickShareClaim() { onClickShareClaim() {
this.copyToClipboard("A link to this page", this.windowLocation); this.copyToClipboard("A link to this page", this.deepLinkUrl);
window.navigator.share({ window.navigator.share({
title: "Help Connect Me", title: "Help Connect Me",
text: "I'm trying to find the people who recorded this. Can you help 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 { Vue, Component, Prop } from "vue-facing-decorator";
import { NotificationIface, USE_DEXIE_DB } from "../constants/app"; import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
import { import { createAndSubmitOffer } from "../libs/endorserServer";
createAndSubmitOffer,
serverMessageForUser,
} from "../libs/endorserServer";
import * as libsUtil from "../libs/util"; import * as libsUtil from "../libs/util";
import * as databaseUtil from "../db/databaseUtil"; import * as databaseUtil from "../db/databaseUtil";
import { retrieveSettingsForActiveAccount } from "../db/index"; import { retrieveSettingsForActiveAccount } from "../db/index";

View File

@@ -44,8 +44,7 @@ export default class TopMessage extends Vue {
settings.apiServer === AppString.PROD_ENDORSER_API_SERVER settings.apiServer === AppString.PROD_ENDORSER_API_SERVER
) { ) {
const didPrefix = settings.activeDid?.slice(11, 15); const didPrefix = settings.activeDid?.slice(11, 15);
this.message = this.message = "You are using prod, user " + didPrefix;
"You are using prod, user " + didPrefix;
} }
} catch (err: unknown) { } catch (err: unknown) {
this.$notify( this.$notify(

View File

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

View File

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

View File

@@ -1074,7 +1074,8 @@ export async function generateEndorserJwtUrlForAccount(
const vcJwt = await createEndorserJwtForDid(account.did, contactInfo); 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; return viewPrefix + vcJwt;
} }

View File

@@ -882,6 +882,71 @@ export const contactToCsvLine = (contact: Contact): string => {
return fields.join(","); 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 * 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 { handleApiError } from "./services/api";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { DeepLinkHandler } from "./services/deepLinks"; import { DeepLinkHandler } from "./services/deepLinks";
import { logConsoleAndDb } from "./db/databaseUtil"; import { logger, safeStringify } from "./utils/logger";
import { logger } from "./utils/logger";
logger.log("[Capacitor] Starting initialization"); logger.log("[Capacitor] Starting initialization");
logger.log("[Capacitor] Platform:", process.env.VITE_PLATFORM); logger.log("[Capacitor] Platform:", process.env.VITE_PLATFORM);
@@ -72,10 +71,10 @@ const handleDeepLink = async (data: { url: string }) => {
await router.isReady(); await router.isReady();
await deepLinkHandler.handleDeepLink(data.url); await deepLinkHandler.handleDeepLink(data.url);
} catch (error) { } catch (error) {
logConsoleAndDb("[DeepLink] Error handling deep link: " + error, true); logger.error("[DeepLink] Error handling deep link: ", error);
handleApiError( handleApiError(
{ {
message: error instanceof Error ? error.message : String(error), message: error instanceof Error ? error.message : safeStringify(error),
} as AxiosError, } as AxiosError,
"deep-link", "deep-link",
); );

View File

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

View File

@@ -6,7 +6,7 @@
*/ */
import { AxiosError } from "axios"; 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. * 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) => { export const handleApiError = (error: AxiosError, endpoint: string) => {
if (process.env.VITE_PLATFORM === "capacitor") { 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, message: error.message,
status: error.response?.status, status: error.response?.status,
data: error.response?.data, data: error.response?.data,

View File

@@ -27,18 +27,16 @@
* timesafari://<route>[/<param>][?queryParam1=value1&queryParam2=value2] * timesafari://<route>[/<param>][?queryParam1=value1&queryParam2=value2]
* *
* Supported Routes: * 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: View claim
* - claim-cert: View claim certificate
* - claim-add-raw: Add raw claim * - claim-add-raw: Add raw claim
* - contact-edit: Edit contact * - claim-cert: View claim certificate
* - contacts: View contacts * - confirm-gift
* - contact-import: Import contacts
* - did: View DID * - did: View DID
* - invite-one-accept: Accept invitation
* - onboard-meeting-members
* - project: View project details
* - user-profile: View user profile
* *
* @example * @example
* const handler = new DeepLinkHandler(router); * const handler = new DeepLinkHandler(router);
@@ -81,15 +79,16 @@ export class DeepLinkHandler {
string, string,
{ name: string; paramKey?: string } { name: string; paramKey?: string }
> = { > = {
"claim": { name: "claim" }, // note that similar lists are in src/interfaces/deepLinks.ts
claim: { name: "claim" },
"claim-add-raw": { name: "claim-add-raw" }, "claim-add-raw": { name: "claim-add-raw" },
"claim-cert": { name: "claim-cert" }, "claim-cert": { name: "claim-cert" },
"confirm-gift": { name: "confirm-gift" }, "confirm-gift": { name: "confirm-gift" },
"did": { name: "did", paramKey: "did" }, "contact-import": { name: "contact-import", paramKey: "jwt" },
"invite-one-accept": { name: "invite-one-accept" }, did: { name: "did", paramKey: "did" },
"invite-one-accept": { name: "invite-one-accept", paramKey: "jwt" },
"onboard-meeting-members": { name: "onboard-meeting-members" }, "onboard-meeting-members": { name: "onboard-meeting-members" },
"onboard-meeting-setup": { name: "onboard-meeting-setup" }, project: { name: "project" },
"project": { name: "project" },
"user-profile": { name: "user-profile" }, "user-profile": { name: "user-profile" },
}; };
@@ -99,7 +98,7 @@ export class DeepLinkHandler {
* *
* @param url - The deep link URL to parse (format: scheme://path[?query]) * @param url - The deep link URL to parse (format: scheme://path[?query])
* @throws {DeepLinkError} If URL format is invalid * @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) { private parseDeepLink(url: string) {
const parts = url.split("://"); const parts = url.split("://");
@@ -115,7 +114,16 @@ export class DeepLinkHandler {
}); });
const [path, queryString] = parts[1].split("?"); 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 // Validate route exists before proceeding
if (!this.ROUTE_MAP[routePath]) { if (!this.ROUTE_MAP[routePath]) {
@@ -134,45 +142,14 @@ export class DeepLinkHandler {
} }
const params: Record<string, string> = {}; const params: Record<string, string> = {};
if (param) { if (pathParams) {
// Now we know routePath exists in ROUTE_MAP // Now we know routePath exists in ROUTE_MAP
const routeConfig = this.ROUTE_MAP[routePath]; const routeConfig = this.ROUTE_MAP[routePath];
params[routeConfig.paramKey ?? "id"] = param; params[routeConfig.paramKey ?? "id"] = pathParams.join("/");
} }
return { path: routePath, params, query }; 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. * Routes the deep link to appropriate view with validated parameters.
* Validates route and parameters using Zod schemas before routing. * Validates route and parameters using Zod schemas before routing.
@@ -243,6 +220,39 @@ export class DeepLinkHandler {
code: "INVALID_PARAMETERS", code: "INVALID_PARAMETERS",
message: (error as Error).message, message: (error as Error).message,
details: error, 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

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

View File

@@ -49,21 +49,32 @@
v-if="veriClaim.id" v-if="veriClaim.id"
:to="'/claim-cert/' + encodeURIComponent(veriClaim.id)" :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)"
class="text-blue-500 mt-2" class="text-blue-500 mt-2"
title="Printable Certificate" title="View Printable Certificate"
> >
<font-awesome <font-awesome
icon="square" icon="square"
class="text-white bg-yellow-500 p-1" class="text-white bg-yellow-500 p-1"
/> />
</router-link> </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> </div>
<!-- show link icon to copy this URL to the clipboard --> <!-- show link icon to copy this URL to the clipboard -->
<div class="flex justify-end w-full"> <div class="flex justify-end w-full">
<button <button
title="Copy Link" title="Copy Link"
@click=" @click="copyToClipboard('A link to this page', windowDeepLink)"
copyToClipboard('A link to this page', window.location.href)
"
> >
<font-awesome icon="link" class="text-slate-500" /> <font-awesome icon="link" class="text-slate-500" />
</button> </button>
@@ -405,7 +416,7 @@
contacts can see more details: contacts can see more details:
<a <a
class="text-blue-500" 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 >click to copy this page info</a
> >
and see if they can make an introduction. Someone is connected to and see if they can make an introduction. Someone is connected to
@@ -428,7 +439,7 @@
If you'd like an introduction, If you'd like an introduction,
<a <a
class="text-blue-500" 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 >share this page with them and ask if they'll tell you more about
about the participants.</a about the participants.</a
> >
@@ -546,7 +557,7 @@ import { useClipboard } from "@vueuse/core";
import { GenericVerifiableCredential } from "../interfaces"; import { GenericVerifiableCredential } from "../interfaces";
import GiftedDialog from "../components/GiftedDialog.vue"; import GiftedDialog from "../components/GiftedDialog.vue";
import QuickNav from "../components/QuickNav.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 * as databaseUtil from "../db/databaseUtil";
import { db } from "../db/index"; import { db } from "../db/index";
import { logConsoleAndDb } from "../db/databaseUtil"; import { logConsoleAndDb } from "../db/databaseUtil";
@@ -593,8 +604,9 @@ export default class ClaimView extends Vue {
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
veriClaimDump = ""; veriClaimDump = "";
veriClaimDidsVisible: { [key: string]: string[] } = {}; 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; R = R;
yaml = yaml; yaml = yaml;
libsUtil = libsUtil; libsUtil = libsUtil;
@@ -671,6 +683,7 @@ export default class ClaimView extends Vue {
5000, 5000,
); );
} }
this.windowDeepLink = `${APP_SERVER}/deep-link/claim/${claimId}`;
this.canShare = !!navigator.share; this.canShare = !!navigator.share;
} }
@@ -1006,11 +1019,11 @@ export default class ClaimView extends Vue {
} }
onClickShareClaim() { onClickShareClaim() {
this.copyToClipboard("A link to this page", this.windowLocation); this.copyToClipboard("A link to this page", this.windowDeepLink);
window.navigator.share({ window.navigator.share({
title: "Help Connect Me", title: "Help Connect Me",
text: "I'm trying to find the people who recorded this. Can you help me?", text: "I'm trying to find the people who recorded this. Can you help me?",
url: this.windowLocation, url: this.windowDeepLink,
}); });
} }

View File

@@ -436,7 +436,7 @@ import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import { RouteLocationNormalizedLoaded, Router } from "vue-router"; import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import QuickNav from "../components/QuickNav.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 { db, retrieveSettingsForActiveAccount } from "../db/index"; import { db, retrieveSettingsForActiveAccount } from "../db/index";
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import * as databaseUtil from "../db/databaseUtil"; import * as databaseUtil from "../db/databaseUtil";
@@ -494,7 +494,7 @@ export default class ConfirmGiftView extends Vue {
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
veriClaimDump = ""; veriClaimDump = "";
veriClaimDidsVisible: { [key: string]: string[] } = {}; veriClaimDidsVisible: { [key: string]: string[] } = {};
windowLocation = window.location.href; windowLocation = window.location.href; // this is changed to a deep link in the setup
R = R; R = R;
yaml = yaml; yaml = yaml;
@@ -566,6 +566,9 @@ export default class ConfirmGiftView extends Vue {
} }
const claimId = decodeURIComponent(pathParam); const claimId = decodeURIComponent(pathParam);
this.windowLocation = APP_SERVER + "/deep-link/confirm-gift/" + claimId;
await this.loadClaim(claimId, this.activeDid); await this.loadClaim(claimId, this.activeDid);
} }
@@ -676,12 +679,12 @@ export default class ConfirmGiftView extends Vue {
/** /**
* Add participant (giver/recipient) name & URL info * Add participant (giver/recipient) name & URL info
*/ */
this.giverName = this.didInfo(this.giveDetails?.agentDid);
if (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.urlForNewGive += `&giverDid=${encodeURIComponent(this.giveDetails.agentDid)}&giverName=${encodeURIComponent(this.giverName)}`;
} }
this.recipientName = this.didInfo(this.giveDetails?.recipientDid);
if (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)}`; this.urlForNewGive += `&recipientDid=${encodeURIComponent(this.giveDetails.recipientDid)}&recipientName=${encodeURIComponent(this.recipientName)}`;
} }

View File

@@ -104,6 +104,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Buffer } from "buffer/";
import QRCodeVue3 from "qr-code-generator-vue3"; import QRCodeVue3 from "qr-code-generator-vue3";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
@@ -117,14 +118,20 @@ import { db } from "../db/index";
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import { getContactJwtFromJwtUrl } from "../libs/crypto"; import { getContactJwtFromJwtUrl } from "../libs/crypto";
import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc"; import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc";
import * as libsUtil from "../libs/util";
import { retrieveSettingsForActiveAccount } from "../db/index"; import { retrieveSettingsForActiveAccount } from "../db/index";
import * as databaseUtil from "../db/databaseUtil"; 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 UserNameDialog from "../components/UserNameDialog.vue";
import { generateEndorserJwtUrlForAccount } from "../libs/endorserServer";
import { retrieveAccountMetadata } from "../libs/util"; import { retrieveAccountMetadata } from "../libs/util";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { parseJsonField } from "../db/databaseUtil"; import { parseJsonField } from "../db/databaseUtil";
import { Account } from "@/db/tables/accounts";
interface QRScanResult { interface QRScanResult {
rawValue?: string; rawValue?: string;
@@ -142,7 +149,7 @@ interface IUserNameDialog {
UserNameDialog, UserNameDialog,
}, },
}) })
export default class ContactQRScan extends Vue { export default class ContactQRScanFull extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
$router!: Router; $router!: Router;
@@ -151,6 +158,8 @@ export default class ContactQRScan extends Vue {
activeDid = ""; activeDid = "";
apiServer = ""; apiServer = "";
givenName = ""; givenName = "";
isRegistered = false;
profileImageUrl = "";
qrValue = ""; qrValue = "";
ETHR_DID_PREFIX = ETHR_DID_PREFIX; ETHR_DID_PREFIX = ETHR_DID_PREFIX;
@@ -172,19 +181,22 @@ export default class ContactQRScan extends Vue {
this.activeDid = settings.activeDid || ""; this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
this.givenName = settings.firstName || ""; this.givenName = settings.firstName || "";
this.isRegistered = !!settings.isRegistered;
this.profileImageUrl = settings.profileImageUrl || "";
const account = await retrieveAccountMetadata(this.activeDid); const account = await retrieveAccountMetadata(this.activeDid);
if (account) { if (account) {
const name = const name =
(settings.firstName || "") + (settings.firstName || "") +
(settings.lastName ? ` ${settings.lastName}` : ""); (settings.lastName ? ` ${settings.lastName}` : "");
this.qrValue = await generateEndorserJwtUrlForAccount( const publicKeyBase64 = Buffer.from(
account, account.publicKeyHex,
!!settings.isRegistered, "hex",
name, ).toString("base64");
settings.profileImageUrl || "", this.qrValue =
false, CONTACT_CSV_HEADER +
); "\n" +
`"${name}",${account.did},${publicKeyBase64},false,${this.isRegistered}`;
} }
} catch (error) { } catch (error) {
logger.error("Error initializing component:", { logger.error("Error initializing component:", {
@@ -336,57 +348,69 @@ export default class ContactQRScan extends Vue {
logger.info("Processing QR code scan result:", rawValue); logger.info("Processing QR code scan result:", rawValue);
// Extract JWT let contact: Contact;
const jwt = getContactJwtFromJwtUrl(rawValue); if (rawValue.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI)) {
if (!jwt) { // Extract JWT
logger.warn("Invalid QR code format - no JWT found in URL"); 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({ this.$notify({
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Invalid QR Code", title: "Error",
text: "This QR code does not contain valid contact information. Please scan a TimeSafari contact QR code.", 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; 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 // Add contact but keep scanning
logger.info("Adding new contact to database:", { logger.info("Adding new contact to database:", {
did: contact.did, did: contact.did,
@@ -468,7 +492,7 @@ export default class ContactQRScan extends Vue {
title: "Contact Exists", title: "Contact Exists",
text: "This contact has already been added to your list.", text: "This contact has already been added to your list.",
}, },
3000, 5000,
); );
return; 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() useClipboard()
.copy(this.qrValue) .copy(jwtUrl)
.then(() => { .then(() => {
this.$notify( this.$notify(
{ {

View File

@@ -159,6 +159,7 @@
<script lang="ts"> <script lang="ts">
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { Buffer } from "buffer/";
import QRCodeVue3 from "qr-code-generator-vue3"; import QRCodeVue3 from "qr-code-generator-vue3";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
@@ -174,17 +175,20 @@ import * as databaseUtil from "../db/databaseUtil";
import { parseJsonField } from "../db/databaseUtil"; import { parseJsonField } from "../db/databaseUtil";
import { getContactJwtFromJwtUrl } from "../libs/crypto"; import { getContactJwtFromJwtUrl } from "../libs/crypto";
import { import {
CONTACT_CSV_HEADER,
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
generateEndorserJwtUrlForAccount, generateEndorserJwtUrlForAccount,
register, register,
setVisibilityUtil, setVisibilityUtil,
} from "../libs/endorserServer"; } from "../libs/endorserServer";
import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc"; 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 { Router } from "vue-router";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { QRScannerFactory } from "@/services/QRScanner/QRScannerFactory"; import { QRScannerFactory } from "@/services/QRScanner/QRScannerFactory";
import { CameraState } from "@/services/QRScanner/types"; import { CameraState } from "@/services/QRScanner/types";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { Account } from "@/db/tables/accounts";
interface QRScanResult { interface QRScanResult {
rawValue?: string; rawValue?: string;
@@ -214,6 +218,7 @@ export default class ContactQRScanShow extends Vue {
isRegistered = false; isRegistered = false;
qrValue = ""; qrValue = "";
isScanning = false; isScanning = false;
profileImageUrl = "";
error: string | null = null; error: string | null = null;
// QR Scanner properties // QR Scanner properties
@@ -251,19 +256,21 @@ export default class ContactQRScanShow extends Vue {
this.hideRegisterPromptOnNewContact = this.hideRegisterPromptOnNewContact =
!!settings.hideRegisterPromptOnNewContact; !!settings.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings.isRegistered; this.isRegistered = !!settings.isRegistered;
this.profileImageUrl = settings.profileImageUrl || "";
const account = await retrieveAccountMetadata(this.activeDid); const account = await libsUtil.retrieveAccountMetadata(this.activeDid);
if (account) { if (account) {
const name = const name =
(settings.firstName || "") + (settings.firstName || "") +
(settings.lastName ? ` ${settings.lastName}` : ""); (settings.lastName ? ` ${settings.lastName}` : "");
this.qrValue = await generateEndorserJwtUrlForAccount( const publicKeyBase64 = Buffer.from(
account, account.publicKeyHex,
!!settings.isRegistered, "hex",
name, ).toString("base64");
settings.profileImageUrl || "", this.qrValue =
false, CONTACT_CSV_HEADER +
); "\n" +
`"${name}",${account.did},${publicKeyBase64},false,${this.isRegistered}`;
} }
} catch (error) { } catch (error) {
logger.error("Error initializing component:", { logger.error("Error initializing component:", {
@@ -274,7 +281,7 @@ export default class ContactQRScanShow extends Vue {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Initialization Error", 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); logger.info("Processing QR code scan result:", rawValue);
// Extract JWT let contact: Contact;
const jwt = getContactJwtFromJwtUrl(rawValue); if (rawValue.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI)) {
if (!jwt) { const jwt = getContactJwtFromJwtUrl(rawValue);
logger.warn("Invalid QR code format - no JWT found in URL"); 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({ this.$notify({
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Invalid QR Code", title: "Error",
text: "This QR code does not contain valid contact information. Please scan a TimeSafari contact QR code.", 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; 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 // Add contact but keep scanning
logger.info("Adding new contact to database:", { logger.info("Adding new contact to database:", {
did: contact.did, did: contact.did,
@@ -649,12 +671,20 @@ export default class ContactQRScanShow extends Vue {
}); });
} }
onCopyUrlToClipboard() { async onCopyUrlToClipboard() {
//this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing const account = (await libsUtil.retrieveFullyDecryptedAccount(
this.activeDid,
)) as Account;
const jwtUrl = await generateEndorserJwtUrlForAccount(
account,
this.isRegistered,
this.givenName,
this.profileImageUrl,
true,
);
useClipboard() useClipboard()
.copy(this.qrValue) .copy(jwtUrl)
.then(() => { .then(() => {
// console.log("Contact URL:", this.qrValue);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
@@ -772,7 +802,7 @@ export default class ContactQRScanShow extends Vue {
title: "Contact Exists", title: "Contact Exists",
text: "This contact has already been added to your list.", text: "This contact has already been added to your list.",
}, },
3000, 5000,
); );
return; return;
} }

View File

@@ -126,7 +126,6 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
v-if="showGiveNumbers" 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="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()" :class="showGiveAmountsClassNames()"
@click="toggleShowGiveTotals()" @click="toggleShowGiveTotals()"
@@ -142,7 +141,6 @@
</button> </button>
<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" 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()" @click="toggleShowContactAmounts()"
> >
@@ -493,7 +491,7 @@ export default class ContactsView extends Vue {
private async processContactJwt() { private async processContactJwt() {
// handle a contact sent via URL // 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. // because that will do better error checking for things like missing data on iOS platforms.
const importedContactJwt = this.$route.query["contactJwt"] as string; const importedContactJwt = this.$route.query["contactJwt"] as string;
if (importedContactJwt) { if (importedContactJwt) {
@@ -619,7 +617,7 @@ export default class ContactsView extends Vue {
title: "Error with Invite", title: "Error with Invite",
text: message, text: message,
}, },
5000, -1,
); );
} }
// if we're here, they haven't redirected anywhere, so we'll redirect here without a query parameter // 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( private async addContactFromEndorserMobileLine(
line: string, lineRaw: string,
): Promise<IndexableType> { ): Promise<IndexableType> {
// Note that Endorser Mobile puts name first, then did, etc. const newContact = libsUtil.csvLineToContact(lineRaw);
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 platformService = PlatformServiceFactory.getInstance(); const platformService = PlatformServiceFactory.getInstance();
const { sql, params } = databaseUtil.generateInsertStatement( const { sql, params } = databaseUtil.generateInsertStatement(
newContact as unknown as Record<string, unknown>, newContact as unknown as Record<string, unknown>,
@@ -1160,7 +1122,7 @@ export default class ContactsView extends Vue {
(regResult.error as string) || (regResult.error as string) ||
"Something went wrong during registration.", "Something went wrong during registration.",
}, },
5000, -1,
); );
} }
} catch (error) { } catch (error) {
@@ -1194,7 +1156,7 @@ export default class ContactsView extends Vue {
title: "Registration Error", title: "Registration Error",
text: userMessage, text: userMessage,
}, },
5000, -1,
); );
} }
} }
@@ -1215,7 +1177,6 @@ export default class ContactsView extends Vue {
); );
if (result.success) { if (result.success) {
//contact.seesMe = visibility; // why doesn't it affect the UI from here? //contact.seesMe = visibility; // why doesn't it affect the UI from here?
//console.log("Set result & seesMe", result, contact.seesMe, contact.did);
if (showSuccessAlert) { if (showSuccessAlert) {
this.$notify( this.$notify(
{ {
@@ -1431,14 +1392,11 @@ export default class ContactsView extends Vue {
} }
return contact; return contact;
}); });
// console.log(
// "Array of selected contacts:",
// JSON.stringify(selectedContacts),
// );
const contactsJwt = await createEndorserJwtForDid(this.activeDid, { const contactsJwt = await createEndorserJwtForDid(this.activeDid, {
contacts: selectedContacts, contacts: selectedContacts,
}); });
const contactsJwtUrl = APP_SERVER + "/contact-import/" + contactsJwt; const contactsJwtUrl =
APP_SERVER + "/deep-link/contact-import/" + contactsJwt;
useClipboard() useClipboard()
.copy(contactsJwtUrl) .copy(contactsJwtUrl)
.then(() => { .then(() => {

View File

@@ -66,9 +66,14 @@ const formattedPath = computed(() => {
const path = originalPath.value.replace(/^\/+/, ""); const path = originalPath.value.replace(/^\/+/, "");
// Log for debugging // Log for debugging
logger.log("Original Path:", originalPath.value); logger.log(
logger.log("Route Params:", route.params); "[DeepLinkError] Original Path:",
logger.log("Route Query:", route.query); originalPath.value,
"Route Params:",
route.params,
"Route Query:",
route.query,
);
return path; 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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) { } catch (e: any) {
logger.error("Error with search all:", e); logger.error("Error with search all: " + errorStringForLog(e));
// this sometimes gives different information
logger.error("Error with search all (error added): " + e);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
@@ -617,7 +615,7 @@ export default class DiscoverView extends Vue {
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) { } catch (e: any) {
logger.error("Error with search local:", e); logger.error("Error with search local: " + errorStringForLog(e));
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",

View File

@@ -37,14 +37,16 @@
<h2 class="text-xl font-semibold">What is the idea here?</h2> <h2 class="text-xl font-semibold">What is the idea here?</h2>
<p> <p>
We are building networks of people who want to grow good society from the ground up, using modern We are building networks of people who want to grow good society from the ground up, using
technology that connects people peer-to-peer. modern technology that connects people peer-to-peer.
First of all, let's showcase gratitude: see what people have given, and recognize First of all, let's showcase gratitude: see what people have given, and recognize gifts
gifts you've seen. This is done in a way that leaves a permanent record -- one that 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 it was for them. This can be came from you, and one that the recipient can prove they were mentioned.
personally gratifying, but it extends to broader work: volunteers get This can be personally gratifying, but it extends to broader work: volunteers get
confirmation of activity, and they can selectively show off their contributions confirmation of activity, and they can selectively show off their contributions and
and network. 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>
<p class="mt-2"> <p class="mt-2">
With this, you highlight giving and you also offer help -- With this, you highlight giving and you also offer help --

View File

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

View File

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

View File

@@ -720,7 +720,7 @@ export default class OnboardMeetingView extends Vue {
onboardMeetingMembersLink(): string { onboardMeetingMembersLink(): string {
if (this.currentMeeting) { 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 || "", this.currentMeeting?.password || "",
)}`; )}`;
} }

View File

@@ -27,6 +27,12 @@
> >
<font-awesome icon="pen" class="text-sm text-blue-500 ml-2 mb-1" /> <font-awesome icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
</button> </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> </h2>
</div> </div>
</div> </div>
@@ -55,7 +61,11 @@
<span class="truncate inline-block max-w-[calc(100%-2rem)]"> <span class="truncate inline-block max-w-[calc(100%-2rem)]">
{{ issuerInfoObject?.displayName }} {{ issuerInfoObject?.displayName }}
</span> </span>
<span class="inline-flex items-center">
<span
v-if="!serverUtil.isHiddenDid(issuer)"
class="inline-flex items-center"
>
<router-link <router-link
:to="{ :to="{
path: '/did/' + encodeURIComponent(issuer), path: '/did/' + encodeURIComponent(issuer),
@@ -113,7 +123,7 @@
class="fa-fw text-slate-400" class="fa-fw text-slate-400"
></font-awesome> ></font-awesome>
<a <a
:href="addScheme(url)" :href="ensureScheme(url)"
target="_blank" target="_blank"
class="underline text-blue-500" class="underline text-blue-500"
> >
@@ -632,7 +642,7 @@ import TopMessage from "../components/TopMessage.vue";
import QuickNav from "../components/QuickNav.vue"; import QuickNav from "../components/QuickNav.vue";
import EntityIcon from "../components/EntityIcon.vue"; import EntityIcon from "../components/EntityIcon.vue";
import ProjectIcon from "../components/ProjectIcon.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 * as databaseUtil from "../db/databaseUtil";
import { import {
db, db,
@@ -646,6 +656,7 @@ import { retrieveAccountDids } from "../libs/util";
import HiddenDidDialog from "../components/HiddenDidDialog.vue"; import HiddenDidDialog from "../components/HiddenDidDialog.vue";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { useClipboard } from "@vueuse/core";
/** /**
* Project View Component * Project View Component
* @author Matthew Raymer * @author Matthew Raymer
@@ -842,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? // Isn't there a better way to make this available to the template?
expandText() { expandText() {
this.expanded = true; this.expanded = true;
@@ -1304,7 +1337,7 @@ export default class ProjectViewView extends Vue {
} }
// return an HTTPS URL if it's not a global URL // return an HTTPS URL if it's not a global URL
addScheme(url: string) { ensureScheme(url: string) {
if (!libsUtil.isGlobalUri(url)) { if (!libsUtil.isGlobalUri(url)) {
return "https://" + url; return "https://" + url;
} }
@@ -1465,7 +1498,13 @@ export default class ProjectViewView extends Vue {
} }
openHiddenDidDialog() { 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( (this.$refs.hiddenDidDialog as HiddenDidDialog).open(
"project/" + shortestProjectId,
"creator", "creator",
this.issuerVisibleToDids, this.issuerVisibleToDids,
this.allContacts, this.allContacts,

View File

@@ -298,24 +298,25 @@ export default class QuickActionBvcBeginView extends Vue {
} }
// in parallel, make a confirmation for each selected claim and send them all to the server // in parallel, make a confirmation for each selected claim and send them all to the server
const confirmResults: PromiseSettledResult<CreateAndSubmitClaimResult>[] = await Promise.allSettled( const confirmResults: PromiseSettledResult<CreateAndSubmitClaimResult>[] =
this.claimsToConfirmSelected.map(async (jwtId) => { await Promise.allSettled(
const record = this.claimsToConfirm.find( this.claimsToConfirmSelected.map(async (jwtId) => {
(claim) => claim.id === jwtId, const record = this.claimsToConfirm.find(
); (claim) => claim.id === jwtId,
if (!record) { );
return { success: false, error: "Record not found." }; if (!record) {
} return { success: false, error: "Record not found." };
return createAndSubmitConfirmation( }
this.activeDid, return createAndSubmitConfirmation(
record.claim as GenericVerifiableCredential, this.activeDid,
record.id, record.claim as GenericVerifiableCredential,
record.handleId, record.id,
this.apiServer, record.handleId,
axios, this.apiServer,
); axios,
}), );
); }),
);
// check for any rejected confirmations // check for any rejected confirmations
const confirmsSucceeded = confirmResults.filter( const confirmsSucceeded = confirmResults.filter(
// 'fulfilled' is the status in a successful PromiseFulfilledResult // 'fulfilled' is the status in a successful PromiseFulfilledResult

View File

@@ -105,7 +105,7 @@ export default class ShareMyContactInfoView extends Vue {
group: "alert", group: "alert",
type: "info", type: "info",
title: "Copied", 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, 5000,
); );

View File

@@ -16,6 +16,7 @@
</button> </button>
Individual Profile Individual Profile
</h1> </h1>
<div class="text-sm text-center text-slate-500"></div>
</div> </div>
<!-- Loading Animation --> <!-- Loading Animation -->
@@ -32,6 +33,12 @@
<div class="text-sm"> <div class="text-sm">
<font-awesome icon="user" class="fa-fw text-slate-400"></font-awesome> <font-awesome icon="user" class="fa-fw text-slate-400"></font-awesome>
{{ didInfo(profile.issuerDid, activeDid, allMyDids, allContacts) }} {{ 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> </div>
<p v-if="profile.description" class="mt-4 text-slate-600"> <p v-if="profile.description" class="mt-4 text-slate-600">
{{ profile.description }} {{ profile.description }}
@@ -100,6 +107,7 @@ import { Router, RouteLocationNormalizedLoaded } from "vue-router";
import QuickNav from "../components/QuickNav.vue"; import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue"; import TopMessage from "../components/TopMessage.vue";
import { import {
APP_SERVER,
DEFAULT_PARTNER_API_SERVER, DEFAULT_PARTNER_API_SERVER,
NotificationIface, NotificationIface,
USE_DEXIE_DB, USE_DEXIE_DB,
@@ -113,6 +121,7 @@ import { retrieveAccountDids } from "../libs/util";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { Settings } from "@/db/tables/settings"; import { Settings } from "@/db/tables/settings";
import { useClipboard } from "@vueuse/core";
@Component({ @Component({
components: { components: {
LMap, LMap,
@@ -186,6 +195,10 @@ export default class UserProfileView extends Vue {
if (response.status === 200) { if (response.status === 200) {
const result = await response.json(); const result = await response.json();
this.profile = result.data; 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 { } else {
throw new Error("Failed to load profile"); throw new Error("Failed to load profile");
} }
@@ -204,5 +217,22 @@ export default class UserProfileView extends Vue {
this.isLoading = false; 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> </script>