Merge branch 'main' of https://github.com/ente-io/auth into mobile_face

This commit is contained in:
Neeraj Gupta 2024-03-13 12:06:33 +05:30
commit f9dd509d61
341 changed files with 6010 additions and 4347 deletions

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
# Set line endings of shell scripts to LF, even on Windows, otherwise execution
# within Docker fails.
*.sh text eol=lf

View file

@ -3,15 +3,16 @@ name: "Sync Crowdin translations (auth)"
on:
push:
paths:
# Run action when auth's intl_en.arb is changed
# Run workflow when auth's intl_en.arb is changed
- "mobile/lib/l10n/arb/app_en.arb"
# Or the workflow itself is changed
- ".github/workflows/auth-crowdin.yml"
branches: [main]
schedule:
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
- cron: "0 */24 * * *"
workflow_dispatch: # Allow manually running the action
# See: [Note: Run every 24 hours]
- cron: "50 1 * * *"
# Also allow manually running the workflow
workflow_dispatch:
jobs:
synchronize-with-crowdin:

View file

@ -1,11 +1,9 @@
name: "Lint (auth)"
on:
# Run on every push to branches (this also covers pull requests)
# Run on every push to a branch other than main that changes auth/
push:
# See: [Note: Specify branch when specifying a path filter]
branches: ["**"]
# Only run if something changes in these paths
branches-ignore: [main]
paths:
- "auth/**"
- ".github/workflows/auth-lint.yml"

View file

@ -29,7 +29,7 @@ on:
- "auth-v*"
env:
FLUTTER_VERSION: "3.16.9"
FLUTTER_VERSION: "3.13.4"
jobs:
build-ubuntu:
@ -118,14 +118,11 @@ jobs:
updateOnlyUnreleased: true
- name: Upload AAB to PlayStore
# Temporarily disable GP upload, enable this once desktop build
# testing is complete.
if: false
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: io.ente.auth
releaseFiles: build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
releaseFiles: auth/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
track: internal
build-windows:

View file

@ -49,6 +49,6 @@ jobs:
project_path: "./cli"
pre_command: export CGO_ENABLED=0
build_flags: "-trimpath"
ldflags: "-s -w"
ldflags: "-X main.AppVersion=${{ github.ref_name }} -s -w"
md5sum: false
sha256sum: true

47
.github/workflows/docs-deploy.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: "Deploy (docs)"
on:
# Run on every push to main that changes docs/
push:
branches: [main]
paths:
- "docs/**"
- ".github/workflows/docs-deploy.yml"
# Also allow manually running the workflow
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: docs
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup node and enable yarn caching
uses: actions/setup-node@v4
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build production site
# Will create docs/.vitepress/dist
run: yarn build
- name: Publish
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: help
directory: docs/docs/.vitepress/dist
wranglerVersion: "3"

37
.github/workflows/docs-verify-build.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: "Verify build (docs)"
# Preflight build of docs. This allows us to ensure that yarn build is
# succeeding before we merge the PR into main.
on:
# Run on every push to a branch other than main that changes docs/
push:
branches-ignore: [main]
paths:
- "docs/**"
- ".github/workflows/docs-verify-build.yml"
jobs:
verify-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: docs
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup node and enable yarn caching
uses: actions/setup-node@v4
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build production site
run: yarn build

View file

@ -3,15 +3,16 @@ name: "Sync Crowdin translations (mobile)"
on:
push:
paths:
# Run action when mobiles's intl_en.arb is changed
# Run workflow when mobiles's intl_en.arb is changed
- "mobile/lib/l10n/intl_en.arb"
# Or the workflow itself is changed
- ".github/workflows/mobile-crowdin.yml"
branches: [main]
schedule:
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
- cron: "0 */24 * * *"
workflow_dispatch: # Allow manually running the action
# See: [Note: Run every 24 hours]
- cron: "40 1 * * *"
# Also allow manually running the workflow
workflow_dispatch:
jobs:
synchronize-with-crowdin:

View file

@ -1,11 +1,9 @@
name: "Lint (mobile)"
on:
# Run on every push (this also covers pull requests)
# Run on every push to a branch other than main that changes mobile/
push:
# See: [Note: Specify branch when specifying a path filter]
branches: ["**"]
# Only run if something changes in these paths
branches-ignore: [main]
paths:
- "mobile/**"
- ".github/workflows/mobile-lint.yml"

View file

@ -39,7 +39,9 @@ jobs:
encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }}
- name: Build independent APK
run: flutter build apk --release --flavor independent && mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente.apk
run: |
flutter build apk --release --flavor independent
mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk
env:
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }}
@ -52,5 +54,5 @@ jobs:
- name: Create a draft GitHub release
uses: ncipollo/release-action@v1
with:
artifacts: "mobile/build/app/outputs/flutter-apk/ente.apk,mobile/build/app/outputs/flutter-apk/sha256sum"
artifacts: "mobile/build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk,mobile/build/app/outputs/flutter-apk/sha256sum"
draft: true

View file

@ -1,11 +1,9 @@
name: "Lint (server)"
on:
# Run on every push (this also covers pull requests)
# Run on every push to a branch other than main that changes server/
push:
# See: [Note: Specify branch when specifying a path filter]
branches: ["**"]
# Only run if something changes in these paths
branches-ignore: [main]
paths:
- "server/**"
- ".github/workflows/server-lint.yml"

View file

@ -3,15 +3,16 @@ name: "Sync Crowdin translations (web)"
on:
push:
paths:
# Run action when web's en-US/translation.json is changed
# Run workflow when web's en-US/translation.json is changed
- "web/apps/photos/public/locales/en-US/translation.json"
# Or the workflow itself is changed
- ".github/workflows/web-crowdin.yml"
branches: [main]
schedule:
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
- cron: "0 */24 * * *"
workflow_dispatch: # Allow manually running the action
# See: [Note: Run every 24 hours]
- cron: "20 1 * * *"
# Also allow manually running the workflow
workflow_dispatch:
jobs:
synchronize-with-crowdin:

View file

@ -0,0 +1,43 @@
name: "Deploy (accounts)"
on:
push:
# Run workflow on pushes to the deploy/accounts
branches: [deploy/accounts]
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup node and enable yarn caching
uses: actions/setup-node@v4
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build accounts
run: yarn build:accounts
- name: Publish accounts
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: deploy/accounts
directory: web/apps/accounts/out
wranglerVersion: "3"

43
.github/workflows/web-deploy-auth.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: "Deploy (auth)"
on:
push:
# Run workflow on pushes to the deploy/auth
branches: [deploy/auth]
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup node and enable yarn caching
uses: actions/setup-node@v4
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build auth
run: yarn build:auth
- name: Publish auth
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: deploy/auth
directory: web/apps/auth/out
wranglerVersion: "3"

43
.github/workflows/web-deploy-cast.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: "Deploy (cast)"
on:
push:
# Run workflow on pushes to the deploy/cast
branches: [deploy/cast]
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup node and enable yarn caching
uses: actions/setup-node@v4
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build cast
run: yarn build:cast
- name: Publish cast
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: deploy/cast
directory: web/apps/cast/out
wranglerVersion: "3"

43
.github/workflows/web-deploy-photos.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: "Deploy (photos)"
on:
push:
# Run workflow on pushes to the deploy/photos
branches: [deploy/photos]
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup node and enable yarn caching
uses: actions/setup-node@v4
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build photos
run: yarn build:photos
- name: Publish photos
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: deploy/photos
directory: web/apps/photos/out
wranglerVersion: "3"

View file

@ -1,20 +1,9 @@
name: "Lint (web)"
on:
# Run on every push (this also covers pull requests)
# Run on every push to a branch other than main that changes web/
push:
# [Note: Specify branch when specifying a path filter]
#
# Path filters are ignored for tag pushes, which causes this workflow to
# always run when we push a tag. Defining an explicit branch solves the
# issue. From GitHub's docs:
#
# > if you define both branches/branches-ignore and paths/paths-ignore,
# > the workflow will only run when both filters are satisfied.
#
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
branches: ["**"]
# Only run if something changes in these paths
branches-ignore: [main]
paths:
- "web/**"
- ".github/workflows/web-lint.yml"

94
.github/workflows/web-nightly.yml vendored Normal file
View file

@ -0,0 +1,94 @@
name: "Nightly (web)"
on:
schedule:
# [Note: Run every 24 hours]
#
# Run every 24 hours - First field is minute, second is hour of the day
# This runs 23:15 UTC everyday - 1 and 15 are just arbitrary offset to
# avoid scheduling it on the exact hour, as suggested by GitHub.
#
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
# https://crontab.guru/
#
- cron: "15 23 * * *"
# Also allow manually running the workflow
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup node and enable yarn caching
uses: actions/setup-node@v4
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build accounts
run: yarn build:accounts
- name: Publish accounts
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: n-accounts
directory: web/apps/accounts/out
wranglerVersion: "3"
- name: Build auth
run: yarn build:auth
- name: Publish auth
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: n-auth
directory: web/apps/auth/out
wranglerVersion: "3"
- name: Build cast
run: yarn build:cast
- name: Publish cast
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: n-cast
directory: web/apps/cast/out
wranglerVersion: "3"
- name: Build photos
run: yarn build:photos
env:
NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT: https://albums.ente.sh
- name: Publish photos
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: n-photos
directory: web/apps/photos/out
wranglerVersion: "3"

52
.github/workflows/web-preview.yml vendored Normal file
View file

@ -0,0 +1,52 @@
name: "Preview (web)"
on:
workflow_dispatch:
inputs:
app:
description: "App to build and deploy"
type: choice
required: true
default: "photos"
options:
- "accounts"
- "auth"
- "cast"
- "photos"
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup node and enable yarn caching
uses: actions/setup-node@v4
with:
node-version: 20
cache: "yarn"
cache-dependency-path: "docs/yarn.lock"
- name: Install dependencies
run: yarn install
- name: Build ${{ inputs.app }}
run: yarn build:${{ inputs.app }}
- name: Publish ${{ inputs.app }} to preview
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: ente
branch: preview
directory: web/apps/${{ inputs.app }}/out
wranglerVersion: "3"

4
.gitmodules vendored
View file

@ -9,10 +9,6 @@
[submodule "auth/assets/simple-icons"]
path = auth/assets/simple-icons
url = https://github.com/simple-icons/simple-icons.git
[submodule "desktop/thirdparty/next-electron-server"]
path = desktop/thirdparty/next-electron-server
url = https://github.com/ente-io/next-electron-server.git
branch = desktop
[submodule "mobile/thirdparty/flutter"]
path = mobile/thirdparty/flutter
url = https://github.com/flutter/flutter.git

View file

@ -50,13 +50,13 @@ Thank you for your support.
## Document
_Coming soon!_
The help guides and FAQs for users of Ente products are also open source, and
can be edited in a wiki-esque manner by our community members. More than the
quantity, we feel this helps improve the quality and approachability of the
documentation by bringing in more diverse viewpoints and familiarity levels.
See [docs/](docs/README.md) for how to edit these documents.
## Code contributions
If you'd like to contribute code, it is best to start small.

View file

@ -70,7 +70,7 @@ existing users will be grandfathered in.
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/app/id6444121398)
[<img height="42" src=".github/assets/play-store-badge.png">](https://play.google.com/store/apps/details?id=io.ente.auth)
[<img height="42" src=".github/assets/f-droid-badge.png">](https://f-droid.org/packages/io.ente.auth/)
[<img height="42" src=".github/assets/github-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Av2.0.34&expanded=true)
[<img height="42" src=".github/assets/github-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
[<img height="42" src=".github/assets/web-badge.svg">](https://auth.ente.io)
</div>

View file

@ -7,7 +7,7 @@ details as possible about whatever it is that you need help with, and we will
get back to you as soon as possible.
In some cases, your query might already have been answered in our help
documentation (_Coming soon!_).
documentation at [help.ente.io](https://help.ente.io).
Other ways to get in touch are:

View file

@ -12,7 +12,7 @@ multi-device sync.
### Android
This repository's [GitHub
releases](https://github.com/ente-io/ente/releases/latest/download/ente-auth.apk)
releases](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
contains APKs, built straight from source. These builds keep themselves updated,
without relying on third party stores.

View file

@ -37,8 +37,7 @@
{
"title": "BorgBase",
"altNames": ["borg"],
"slug": "BorgBase",
"hex": "222C31"
"slug": "BorgBase"
},
{
"title": "Brave Creators",
@ -109,8 +108,7 @@
{
"title": "Gosuslugi",
"altNames": ["Госуслуги"],
"slug": "Gosuslugi",
"hex": "EE2F53"
"slug": "Gosuslugi"
},
{
"title": "Healthchecks.io",
@ -127,13 +125,11 @@
},
{
"title": "IVPN",
"slug": "IVPN",
"hex": "FA3243"
"slug": "IVPN"
},
{
"title": "IceDrive",
"slug": "Icedrive",
"hex": "1F4FD0"
"slug": "Icedrive"
},
{
"title": "Jagex",
@ -154,8 +150,7 @@
"title": "Kite"
},
{
"title": "Koofr",
"hex": "71BA05"
"title": "Koofr"
},
{
"title": "Kraken",
@ -184,8 +179,7 @@
{
"title": "Murena",
"altNames": ["eCloud"],
"slug": "ecloud",
"hex": "EC6A55"
"slug": "ecloud"
},
{
"title": "Microsoft"
@ -230,8 +224,7 @@
},
{
"title": "pCloud",
"slug": "pCloud",
"hex": "1EBCC5"
"slug": "pCloud"
},
{
"title": "Peerberry",
@ -371,8 +364,7 @@
{
"title": "Yandex",
"altNames": ["Ya", "Яндекс"],
"slug": "Yandex",
"hex": "FC3F1D"
"slug": "Yandex"
}
]
}

View file

@ -71,8 +71,6 @@ PODS:
- move_to_background (0.0.1):
- Flutter
- MTBBarcodeScanner (5.0.11)
- open_filex (0.0.2):
- Flutter
- OrderedSet (5.0.0)
- package_info_plus (0.4.5):
- Flutter
@ -126,7 +124,6 @@ DEPENDENCIES:
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
- open_filex (from `.symlinks/plugins/open_filex/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
@ -183,8 +180,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/local_auth_ios/ios"
move_to_background:
:path: ".symlinks/plugins/move_to_background/ios"
open_filex:
:path: ".symlinks/plugins/open_filex/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
@ -214,7 +209,7 @@ SPEC CHECKSUMS:
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
@ -226,7 +221,6 @@ SPEC CHECKSUMS:
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943

View file

@ -167,7 +167,7 @@ class SuperLogging {
await setupLogDir();
}
if (sentryIsEnabled) {
await setupSentry();
setupSentry().ignore();
}
Logger.root.level = Level.ALL;
@ -250,7 +250,7 @@ class SuperLogging {
// add error to sentry queue
if (sentryIsEnabled && rec.error != null) {
await _sendErrorToSentry(rec.error!, null);
_sendErrorToSentry(rec.error!, null).ignore();
}
}

View file

@ -144,7 +144,8 @@
"enterCodeHint": "Enter the 6-digit code from\nyour authenticator app",
"lostDeviceTitle": "Lost device?",
"twoFactorAuthTitle": "Two-factor authentication",
"passkeyAuthTitle": "Passkey authentication",
"passkeyAuthTitle": "Passkey verification",
"verifyPasskey": "Verify passkey",
"recoverAccount": "Recover account",
"enterRecoveryKeyHint": "Enter your recovery key",
"recover": "Recover",
@ -407,7 +408,7 @@
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
"waitingForBrowserRequest": "Waiting for browser request...",
"launchPasskeyUrlAgain": "Launch passkey URL again",
"waitingForVerification": "Waiting for verification...",
"passkey": "Passkey",
"developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
"developerSettings": "Developer settings",

View file

@ -59,6 +59,12 @@
"recreatePassword": "Återskapa lösenord",
"useRecoveryKey": "Använd återställningsnyckel",
"incorrectPasswordTitle": "Felaktigt lösenord",
"welcomeBack": "Välkommen tillbaka!",
"changePassword": "Ändra lösenord",
"cancel": "Avbryt",
"yes": "Ja",
"no": "Nej",
"settings": "Inställningar",
"pleaseTryAgain": "Försök igen",
"existingUser": "Befintlig användare",
"delete": "Radera",
@ -68,9 +74,23 @@
"suggestFeatures": "Föreslå funktionalitet",
"faq": "FAQ",
"faq_q_1": "Hur säkert är ente Auth?",
"scan": "Skanna",
"twoFactorAuthTitle": "Tvåfaktorsautentisering",
"enterRecoveryKeyHint": "Ange din återställningsnyckel",
"noRecoveryKeyTitle": "Ingen återställningsnyckel?",
"enterEmailHint": "Ange din e-postadress",
"invalidEmailTitle": "Ogiltig e-postadress",
"invalidEmailMessage": "Ange en giltig e-postadress.",
"deleteAccount": "Radera konto",
"yesSendFeedbackAction": "Ja, skicka feedback",
"noDeleteAccountAction": "Nej, radera konto",
"createNewAccount": "Skapa nytt konto",
"weakStrength": "Svag",
"strongStrength": "Stark",
"moderateStrength": "Måttligt",
"confirmPassword": "Bekräfta lösenord",
"close": "Stäng",
"language": "Språk",
"searchHint": "Sök...",
"search": "Sök",
"sorryUnableToGenCode": "Tyvärr, det gick inte att generera en kod för {issuerName}",
@ -83,5 +103,45 @@
"copiedNextToClipboard": "Kopierade nästa kod till urklipp",
"error": "Fel",
"recoveryKeyCopiedToClipboard": "Återställningsnyckel kopierad till urklipp",
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel."
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel.",
"saveKey": "Spara nyckel",
"back": "Tillbaka",
"createAccount": "Skapa konto",
"password": "Lösenord",
"privacyPolicyTitle": "Integritetspolicy",
"termsOfServicesTitle": "Villkor",
"encryption": "Kryptering",
"changePasswordTitle": "Ändra lösenord",
"resetPasswordTitle": "Återställ lösenord",
"encryptionKeys": "Krypteringsnycklar",
"continueLabel": "Fortsätt",
"logInLabel": "Logga in",
"logout": "Logga ut",
"areYouSureYouWantToLogout": "Är du säker på att du vill logga ut?",
"yesLogout": "Ja, logga ut",
"invalidKey": "Ogiltig nyckel",
"tryAgain": "Försök igen",
"viewRecoveryKey": "Visa återställningsnyckel",
"confirmRecoveryKey": "Bekräfta återställningsnyckel",
"confirmYourRecoveryKey": "Bekräfta din återställningsnyckel",
"confirm": "Bekräfta",
"copyEmailAddress": "Kopiera e-postadress",
"exportLogs": "Exportera loggar",
"enterYourRecoveryKey": "Ange din återställningsnyckel",
"about": "Om",
"terms": "Villkor",
"warning": "Varning",
"pendingSyncs": "Varning",
"activeSessions": "Aktiva sessioner",
"enterPassword": "Ange lösenord",
"export": "Exportera",
"singIn": "Logga in",
"androidCancelButton": "Avbryt",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"iOSOkButton": "OK",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
}
}

View file

@ -144,6 +144,7 @@
"enterCodeHint": "从你的身份验证器应用中\n输入6位数字代码",
"lostDeviceTitle": "丢失了设备吗?",
"twoFactorAuthTitle": "双因素认证",
"passkeyAuthTitle": "通行密钥认证",
"recoverAccount": "恢复账户",
"enterRecoveryKeyHint": "输入您的恢复密钥",
"recover": "恢复",
@ -404,5 +405,8 @@
"signOutOtherDevices": "登出其他设备",
"doNotSignOut": "不要退登",
"hearUsWhereTitle": "您是如何知道Ente的 (可选的)",
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!"
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
"waitingForBrowserRequest": "正在等待浏览器请求...",
"launchPasskeyUrlAgain": "再次启动 通行密钥 URL",
"passkey": "通行密钥"
}

View file

@ -0,0 +1,13 @@
enum TwoFactorType { totp, passkey }
// ToString for TwoFactorType
String twoFactorTypeToString(TwoFactorType type) {
switch (type) {
case TwoFactorType.totp:
return "totp";
case TwoFactorType.passkey:
return "passkey";
default:
return type.name;
}
}

View file

@ -17,6 +17,28 @@ class PasskeyService {
return response.data!["accountsToken"] as String;
}
Future<bool> isPasskeyRecoveryEnabled() async {
final response = await _enteDio.get(
"/users/two-factor/recovery-status",
);
return response.data!["isPasskeyRecoveryEnabled"] as bool;
}
Future<void> configurePasskeyRecovery(
String secret,
String userEncryptedSecret,
String userSecretNonce,
) async {
await _enteDio.post(
"/users/two-factor/passkeys/configure-recovery",
data: {
"secret": secret,
"userSecretCipher": userEncryptedSecret,
"userSecretNonce": userSecretNonce,
},
);
}
Future<void> openPasskeyPage(BuildContext context) async {
try {
final jwtToken = await getJwtToken();

View file

@ -11,6 +11,7 @@ import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/core/network.dart';
import 'package:ente_auth/events/user_details_changed_event.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/models/api/user/srp.dart';
import 'package:ente_auth/models/delete_account.dart';
import 'package:ente_auth/models/key_attributes.dart';
@ -762,7 +763,11 @@ class UserService {
}
}
Future<void> recoverTwoFactor(BuildContext context, String sessionID) async {
Future<void> recoverTwoFactor(
BuildContext context,
String sessionID,
TwoFactorType type,
) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
@ -770,6 +775,7 @@ class UserService {
_config.getHttpEndpoint() + "/users/two-factor/recover",
queryParameters: {
"sessionID": sessionID,
"twoFactorType": twoFactorTypeToString(type),
},
);
if (response.statusCode == 200) {
@ -778,6 +784,7 @@ class UserService {
MaterialPageRoute(
builder: (BuildContext context) {
return TwoFactorRecoveryPage(
type,
sessionID,
response.data["encryptedSecret"],
response.data["secretDecryptionNonce"],
@ -788,6 +795,7 @@ class UserService {
);
}
} on DioError catch (e) {
await dialog.hide();
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
showToast(context, context.l10n.sessionExpired);
@ -809,6 +817,7 @@ class UserService {
);
}
} catch (e) {
await dialog.hide();
_logger.severe(e);
// ignore: unawaited_futures
showErrorDialog(
@ -823,6 +832,7 @@ class UserService {
Future<void> removeTwoFactor(
BuildContext context,
TwoFactorType type,
String sessionID,
String recoveryKey,
String encryptedSecret,
@ -862,6 +872,7 @@ class UserService {
data: {
"sessionID": sessionID,
"secret": secret,
"twoFactorType": twoFactorTypeToString(type),
},
);
if (response.statusCode == 200) {
@ -881,6 +892,7 @@ class UserService {
);
}
} on DioError catch (e) {
await dialog.hide();
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
showToast(context, "Session expired");
@ -902,6 +914,7 @@ class UserService {
);
}
} catch (e) {
await dialog.hide();
_logger.severe(e);
// ignore: unawaited_futures
showErrorDialog(

View file

@ -1,9 +1,11 @@
import 'dart:convert';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
@ -99,30 +101,50 @@ class _PasskeyPageState extends State<PasskeyPage> {
}
Widget _getBody() {
final l10n = context.l10n;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
l10n.waitingForBrowserRequest,
style: const TextStyle(
height: 1.4,
fontSize: 16,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
context.l10n.waitingForVerification,
style: const TextStyle(
height: 1.4,
fontSize: 16,
),
),
),
const SizedBox(height: 16),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 32),
child: ElevatedButton(
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
onPressed: launchPasskey,
child: Text(l10n.launchPasskeyUrlAgain),
const SizedBox(height: 16),
ButtonWidget(
buttonType: ButtonType.primary,
labelText: context.l10n.verifyPasskey,
onTap: () => launchPasskey(),
),
),
],
const Padding(padding: EdgeInsets.all(30)),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
UserService.instance.recoverTwoFactor(
context,
widget.sessionID,
TwoFactorType.passkey,
);
},
child: Container(
padding: const EdgeInsets.all(10),
child: Center(
child: Text(
context.l10n.recoverAccount,
style: const TextStyle(
decoration: TextDecoration.underline,
fontSize: 12,
),
),
),
),
),
],
),
),
);
}

View file

@ -1,14 +1,7 @@
import 'dart:io';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/network.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/update_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:open_filex/open_filex.dart';
import 'package:url_launcher/url_launcher_string.dart';
class AppUpdateDialog extends StatefulWidget {
@ -114,116 +107,3 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
);
}
}
class ApkDownloaderDialog extends StatefulWidget {
final LatestVersionInfo? versionInfo;
const ApkDownloaderDialog(this.versionInfo, {Key? key}) : super(key: key);
@override
State<ApkDownloaderDialog> createState() => _ApkDownloaderDialogState();
}
class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
String? _saveUrl;
double? _downloadProgress;
@override
void initState() {
super.initState();
_saveUrl = Configuration.instance.getTempDirectory() +
"ente-" +
widget.versionInfo!.name! +
".apk";
_downloadApk();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
title: const Text(
"Downloading...",
style: TextStyle(
fontSize: 16,
),
textAlign: TextAlign.center,
),
content: LinearProgressIndicator(
value: _downloadProgress,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.alternativeColor,
),
),
),
);
}
Future<void> _downloadApk() async {
try {
if (!File(_saveUrl!).existsSync()) {
await Network.instance.getDio().download(
widget.versionInfo!.url!,
_saveUrl,
onReceiveProgress: (count, _) {
setState(() {
_downloadProgress = count / widget.versionInfo!.size!;
});
},
);
}
Navigator.of(context, rootNavigator: true).pop('dialog');
// ignore: unawaited_futures
OpenFilex.open(_saveUrl);
} catch (e) {
Logger("ApkDownloader").severe(e);
final AlertDialog alert = AlertDialog(
title: const Text("Sorry"),
content: const Text("The download could not be completed"),
actions: [
TextButton(
child: const Text(
"Ignore",
style: TextStyle(
color: Colors.white,
),
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop('dialog');
Navigator.of(context, rootNavigator: true).pop('dialog');
},
),
TextButton(
child: Text(
"Retry",
style: TextStyle(
color: Theme.of(context).colorScheme.alternativeColor,
),
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop('dialog');
Navigator.of(context, rootNavigator: true).pop('dialog');
showDialog(
context: context,
builder: (BuildContext context) {
return ApkDownloaderDialog(widget.versionInfo);
},
barrierDismissible: false,
);
},
),
],
);
// ignore: unawaited_futures
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
barrierColor: Colors.black87,
);
return;
}
}
}

View file

@ -21,6 +21,7 @@ import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class SecuritySectionWidget extends StatefulWidget {
const SecuritySectionWidget({Key? key}) : super(key: key);
@ -32,6 +33,7 @@ class SecuritySectionWidget extends StatefulWidget {
class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
final _config = Configuration.instance;
late bool _hasLoggedIn;
final Logger _logger = Logger('SecuritySectionWidget');
@override
void initState() {
@ -75,7 +77,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () => PasskeyService.instance.openPasskeyPage(context),
onTap: () async => await onPasskeyClick(context),
),
sectionOptionSpacing,
MenuItemWidget(
@ -159,6 +161,31 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
);
}
Future<void> onPasskeyClick(BuildContext buildContext) async {
try {
final isPassKeyResetEnabled =
await PasskeyService.instance.isPasskeyRecoveryEnabled();
if (!isPassKeyResetEnabled) {
final Uint8List recoveryKey = Configuration.instance.getRecoveryKey();
final resetKey = CryptoUtil.generateKey();
final resetKeyBase64 = CryptoUtil.bin2base64(resetKey);
final encryptionResult = CryptoUtil.encryptSync(
resetKey,
recoveryKey,
);
await PasskeyService.instance.configurePasskeyRecovery(
resetKeyBase64,
CryptoUtil.bin2base64(encryptionResult.encryptedData!),
CryptoUtil.bin2base64(encryptionResult.nonce!),
);
}
PasskeyService.instance.openPasskeyPage(buildContext).ignore();
} catch (e, s) {
_logger.severe("failed to open passkey page", e, s);
await showGenericErrorDialog(context: context);
}
}
Future<void> updateEmailMFA(bool enableEmailMFA) async {
try {
final UserDetails details =

View file

@ -56,7 +56,8 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
text: context.l10n.unlock,
iconData: Icons.lock_open_outlined,
onTap: () async {
await _showLockScreen(source: "tapUnlock");
// ignore: unawaited_futures
_showLockScreen(source: "tapUnlock");
},
),
),

View file

@ -1,4 +1,5 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/lifecycle_event_handler.dart';
import 'package:flutter/material.dart';
@ -129,7 +130,11 @@ class _TwoFactorAuthenticationPageState
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
UserService.instance.recoverTwoFactor(context, widget.sessionID);
UserService.instance.recoverTwoFactor(
context,
widget.sessionID,
TwoFactorType.totp,
);
},
child: Container(
padding: const EdgeInsets.all(10),

View file

@ -1,6 +1,7 @@
import 'dart:ui';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:flutter/material.dart';
@ -9,8 +10,10 @@ class TwoFactorRecoveryPage extends StatefulWidget {
final String sessionID;
final String encryptedSecret;
final String secretDecryptionNonce;
final TwoFactorType type;
const TwoFactorRecoveryPage(
this.type,
this.sessionID,
this.encryptedSecret,
this.secretDecryptionNonce, {
@ -72,6 +75,7 @@ class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
? () async {
await UserService.instance.removeTwoFactor(
context,
widget.type,
widget.sessionID,
_recoveryKey.text,
widget.encryptedSecret,

View file

@ -197,10 +197,10 @@ packages:
dependency: "direct main"
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.17.2"
computer:
dependency: "direct main"
description:
@ -827,10 +827,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.9.1"
mime:
dependency: transitive
description:
@ -879,14 +879,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.2"
open_filex:
dependency: "direct main"
description:
name: open_filex
sha256: "854aefd72dfd74219dc8c8d1767c34ec1eae64b8399a5be317bddb1ec2108915"
url: "https://pub.dev"
source: hosted
version: "4.3.2"
otp:
dependency: "direct main"
description:
@ -1304,10 +1296,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.11.0"
step_progress_indicator:
dependency: "direct main"
description:
@ -1320,10 +1312,10 @@ packages:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.1"
stream_transform:
dependency: transitive
description:
@ -1368,26 +1360,26 @@ packages:
dependency: transitive
description:
name: test
sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f
sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46"
url: "https://pub.dev"
source: hosted
version: "1.24.9"
version: "1.24.3"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
version: "0.6.0"
test_core:
dependency: transitive
description:
name: test_core
sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a
sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e"
url: "https://pub.dev"
source: hosted
version: "0.5.9"
version: "0.5.3"
timezone:
dependency: transitive
description:
@ -1576,10 +1568,10 @@ packages:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.3.0"
version: "0.1.4-beta"
web_socket_channel:
dependency: transitive
description:
@ -1637,5 +1629,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.2.0-194.0.dev <4.0.0"
dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.10.0"

View file

@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 2.0.35+235
version: 2.0.42+242
publish_to: none
environment:
@ -54,13 +54,11 @@ dependencies:
intl: ^0.18.0
json_annotation: ^4.5.0
local_auth: ^2.1.7
local_auth_android: ^1.0.31
local_auth_ios: ^1.1.3
logging: ^1.0.1
modal_bottom_sheet: ^3.0.0-pre
move_to_background: ^1.0.2
open_filex: ^4.3.2
otp: ^3.1.1
package_info_plus: ^4.1.0
password_strength: ^0.2.0

View file

@ -64,7 +64,14 @@ ente account update --email email@domain.com --dir ~/photos
ente export
```
---
### CLI Docs
You can view more cli documents at [docs](docs/generated/ente.md).
To update the docs, run the following command:
```shell
go run main.go docs
```
## Docker

View file

@ -62,7 +62,7 @@ var updateAccCmd = &cobra.Command{
fmt.Printf("invalid app. Accepted values are 'photos', 'locker', 'auth'")
}
err := ctrl.UpdateAccount(context.Background(), model.UpdateAccountParams{
err := ctrl.UpdateAccount(context.Background(), model.AccountCommandParams{
Email: email,
App: api.StringToApp(app),
ExportDir: &exportDir,
@ -73,12 +73,49 @@ var updateAccCmd = &cobra.Command{
},
}
// Subcommand for 'account update'
var getTokenCmd = &cobra.Command{
Use: "get-token",
Short: "Get token for an account for a specific app",
Run: func(cmd *cobra.Command, args []string) {
recoverWithLog()
app, _ := cmd.Flags().GetString("app")
email, _ := cmd.Flags().GetString("email")
if email == "" {
fmt.Println("email must be specified, use --help for more information")
// print help
return
}
validApps := map[string]bool{
"photos": true,
"locker": true,
"auth": true,
}
if !validApps[app] {
fmt.Printf("invalid app. Accepted values are 'photos', 'locker', 'auth'")
}
err := ctrl.GetToken(context.Background(), model.AccountCommandParams{
Email: email,
App: api.StringToApp(app),
})
if err != nil {
fmt.Printf("Error updating account: %v\n", err)
}
},
}
func init() {
// Add 'config' subcommands to the root command
rootCmd.AddCommand(accountCmd)
// Add 'config' subcommands to the 'config' command
updateAccCmd.Flags().String("dir", "", "update export directory")
updateAccCmd.Flags().String("email", "", "email address of the account to update")
updateAccCmd.Flags().String("email", "", "email address of the account")
updateAccCmd.Flags().String("app", "photos", "Specify the app, default is 'photos'")
accountCmd.AddCommand(listAccCmd, addAccCmd, updateAccCmd)
getTokenCmd.Flags().String("email", "", "email address of the account")
getTokenCmd.Flags().String("app", "photos", "Specify the app, default is 'photos'")
accountCmd.AddCommand(listAccCmd, addAccCmd, updateAccCmd, getTokenCmd)
}

90
cli/cmd/admin.go Normal file
View file

@ -0,0 +1,90 @@
package cmd
import (
"context"
"fmt"
"github.com/ente-io/cli/pkg/model"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"strings"
)
var _adminCmd = &cobra.Command{
Use: "admin",
Short: "Commands for admin actions",
Long: "Commands for admin actions like disable or enabling 2fa, bumping up the storage limit, etc.",
}
var _userDetailsCmd = &cobra.Command{
Use: "get-user-id",
Short: "Get user id",
RunE: func(cmd *cobra.Command, args []string) error {
recoverWithLog()
var flags = &model.AdminActionForUser{}
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Name == "admin-user" {
flags.AdminEmail = f.Value.String()
}
if f.Name == "user" {
flags.UserEmail = f.Value.String()
}
})
return ctrl.GetUserId(context.Background(), *flags)
},
}
var _disable2faCmd = &cobra.Command{
Use: "disable-2fa",
Short: "Disable 2fa for a user",
RunE: func(cmd *cobra.Command, args []string) error {
recoverWithLog()
var flags = &model.AdminActionForUser{}
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Name == "admin-user" {
flags.AdminEmail = f.Value.String()
}
if f.Name == "user" {
flags.UserEmail = f.Value.String()
}
})
fmt.Println("Not supported yet")
return nil
},
}
var _updateFreeUserStorage = &cobra.Command{
Use: "update-subscription",
Short: "Update subscription for the free user",
RunE: func(cmd *cobra.Command, args []string) error {
recoverWithLog()
var flags = &model.AdminActionForUser{}
noLimit := false
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Name == "admin-user" {
flags.AdminEmail = f.Value.String()
}
if f.Name == "user" {
flags.UserEmail = f.Value.String()
}
if f.Name == "no-limit" {
noLimit = strings.ToLower(f.Value.String()) == "true"
}
})
return ctrl.UpdateFreeStorage(context.Background(), *flags, noLimit)
},
}
func init() {
rootCmd.AddCommand(_adminCmd)
_ = _userDetailsCmd.MarkFlagRequired("admin-user")
_ = _userDetailsCmd.MarkFlagRequired("user")
_userDetailsCmd.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
_userDetailsCmd.Flags().StringP("user", "u", "", "The email of the user to fetch details for. (required)")
_disable2faCmd.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
_disable2faCmd.Flags().StringP("user", "u", "", "The email of the user to disable 2FA for. (required)")
_updateFreeUserStorage.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
_updateFreeUserStorage.Flags().StringP("user", "u", "", "The email of the user to update subscription for. (required)")
// add a flag with no value --no-limit
_updateFreeUserStorage.Flags().String("no-limit", "True", "When true, sets 100TB as storage limit, and expiry to current date + 100 years")
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _updateFreeUserStorage)
}

View file

@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"github.com/ente-io/cli/pkg"
"github.com/spf13/cobra/doc"
"os"
"runtime"
@ -11,7 +12,7 @@ import (
"github.com/spf13/cobra"
)
const AppVersion = "0.1.11"
var version string
var ctrl *pkg.ClICtrl
@ -27,10 +28,15 @@ var rootCmd = &cobra.Command{
},
}
func GenerateDocs() error {
return doc.GenMarkdownTree(rootCmd, "./docs/generated")
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute(controller *pkg.ClICtrl) {
func Execute(controller *pkg.ClICtrl, ver string) {
ctrl = controller
version = ver
err := rootCmd.Execute()
if err != nil {
os.Exit(1)

View file

@ -12,7 +12,7 @@ var versionCmd = &cobra.Command{
Short: "Prints the current version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Version %s\n", AppVersion)
fmt.Printf("Version %s\n", version)
},
}

10
cli/config.yaml.example Normal file
View file

@ -0,0 +1,10 @@
# You can put this configuration file in the following locations:
# - $HOME/.ente/config.yaml
# - config.yaml in the current working directory
# - $ENTE_CLI_CONFIG_PATH/config.yaml
endpoint:
api: "http://localhost:8080"
log:
http: false # log status code & time taken by requests

View file

@ -0,0 +1,28 @@
## ente
CLI tool for exporting your photos from ente.io
### Synopsis
Start by creating a config file in your home directory:
```
ente [flags]
```
### Options
```
-h, --help help for ente
-t, --toggle Help message for toggle
```
### SEE ALSO
* [ente account](ente_account.md) - Manage account settings
* [ente admin](ente_admin.md) - Commands for admin actions
* [ente auth](ente_auth.md) - Authenticator commands
* [ente export](ente_export.md) - Starts the export process
* [ente version](ente_version.md) - Prints the current version
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,19 @@
## ente account
Manage account settings
### Options
```
-h, --help help for account
```
### SEE ALSO
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
* [ente account add](ente_account_add.md) - Add a new account
* [ente account get-token](ente_account_get-token.md) - Get token for an account for a specific app
* [ente account list](ente_account_list.md) - list configured accounts
* [ente account update](ente_account_update.md) - Update an existing account's export directory
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,19 @@
## ente account add
Add a new account
```
ente account add [flags]
```
### Options
```
-h, --help help for add
```
### SEE ALSO
* [ente account](ente_account.md) - Manage account settings
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,21 @@
## ente account get-token
Get token for an account for a specific app
```
ente account get-token [flags]
```
### Options
```
--app string Specify the app, default is 'photos' (default "photos")
--email string email address of the account
-h, --help help for get-token
```
### SEE ALSO
* [ente account](ente_account.md) - Manage account settings
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,19 @@
## ente account list
list configured accounts
```
ente account list [flags]
```
### Options
```
-h, --help help for list
```
### SEE ALSO
* [ente account](ente_account.md) - Manage account settings
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,22 @@
## ente account update
Update an existing account's export directory
```
ente account update [flags]
```
### Options
```
--app string Specify the app, default is 'photos' (default "photos")
--dir string update export directory
--email string email address of the account
-h, --help help for update
```
### SEE ALSO
* [ente account](ente_account.md) - Manage account settings
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,22 @@
## ente admin
Commands for admin actions
### Synopsis
Commands for admin actions like disable or enabling 2fa, bumping up the storage limit, etc.
### Options
```
-h, --help help for admin
```
### SEE ALSO
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
* [ente admin disable-2fa](ente_admin_disable-2fa.md) - Disable 2fa for a user
* [ente admin get-user-id](ente_admin_get-user-id.md) - Get user id
* [ente admin update-subscription](ente_admin_update-subscription.md) - Update subscription for the free user
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,21 @@
## ente admin disable-2fa
Disable 2fa for a user
```
ente admin disable-2fa [flags]
```
### Options
```
-a, --admin-user string The email of the admin user. (required)
-h, --help help for disable-2fa
-u, --user string The email of the user to disable 2FA for. (required)
```
### SEE ALSO
* [ente admin](ente_admin.md) - Commands for admin actions
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,21 @@
## ente admin get-user-id
Get user id
```
ente admin get-user-id [flags]
```
### Options
```
-a, --admin-user string The email of the admin user. (required)
-h, --help help for get-user-id
-u, --user string The email of the user to fetch details for. (required)
```
### SEE ALSO
* [ente admin](ente_admin.md) - Commands for admin actions
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,22 @@
## ente admin update-subscription
Update subscription for the free user
```
ente admin update-subscription [flags]
```
### Options
```
-a, --admin-user string The email of the admin user. (required)
-h, --help help for update-subscription
--no-limit string When true, sets 100TB as storage limit, and expiry to current date + 100 years (default "True")
-u, --user string The email of the user to update subscription for. (required)
```
### SEE ALSO
* [ente admin](ente_admin.md) - Commands for admin actions
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,16 @@
## ente auth
Authenticator commands
### Options
```
-h, --help help for auth
```
### SEE ALSO
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
* [ente auth decrypt](ente_auth_decrypt.md) - Decrypt authenticator export
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,19 @@
## ente auth decrypt
Decrypt authenticator export
```
ente auth decrypt [input] [output] [flags]
```
### Options
```
-h, --help help for decrypt
```
### SEE ALSO
* [ente auth](ente_auth.md) - Authenticator commands
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,19 @@
## ente export
Starts the export process
```
ente export [flags]
```
### Options
```
-h, --help help for export
```
### SEE ALSO
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
###### Auto generated by spf13/cobra on 13-Mar-2024

View file

@ -0,0 +1,19 @@
## ente version
Prints the current version
```
ente version [flags]
```
### Options
```
-h, --help help for version
```
### SEE ALSO
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
###### Auto generated by spf13/cobra on 13-Mar-2024

27
cli/docs/selfhost.md Normal file
View file

@ -0,0 +1,27 @@
## Self Hosting
If you are self-hosting the server, you can still configure CLI to export data & perform basic admin actions.
To do this, first configure the CLI to point to your server.
Define a config.yaml and put it either in the same directory as CLI binary or path defined in env variable `ENTE_CLI_CONFIG_PATH`
```yaml
endpoint:
api: "http://localhost:8080"
```
You should be able to [add an account](https://github.com/ente-io/ente/blob/main/cli/docs/generated/ente_account_add.md), and subsequently increase the [storage and account validity](https://github.com/ente-io/ente/blob/main/cli/docs/generated/ente_admin_update-subscription.md) using the CLI.
For the admin actions, you first need to whitelist admin users. You can create `server/museum.yaml`, and whitelist add the admin userID `internal.admins`. See [local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml#L211C1-L232C1) in the server source code for details about how to define this.
You can use [account list](https://github.com/ente-io/ente/blob/main/cli/docs/generated/ente_account_list.md) command to find the user id of any account.
```yaml
# ....
internal:
admins:
# - 1580559962386440
# ....
```

View file

@ -12,10 +12,12 @@ require (
require (
github.com/alessio/shellescape v1.4.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
)
require (

View file

@ -48,6 +48,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
@ -168,6 +169,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=

57
cli/internal/api/admin.go Normal file
View file

@ -0,0 +1,57 @@
package api
import (
"context"
"fmt"
"github.com/ente-io/cli/internal/api/models"
"time"
)
func (c *Client) GetUserIdFromEmail(ctx context.Context, email string) (*models.UserDetails, error) {
var res models.UserDetails
r, err := c.restClient.R().
SetContext(ctx).
SetResult(&res).
SetQueryParam("email", email).
Get("/admin/user/")
if err != nil {
return nil, err
}
if r.IsError() {
return nil, &ApiError{
StatusCode: r.StatusCode(),
Message: r.String(),
}
}
return &res, nil
}
func (c *Client) UpdateFreePlanSub(ctx context.Context, userDetails *models.UserDetails, storageInBytes int64, expiryTimeInMicro int64) error {
var res interface{}
if userDetails.Subscription.ProductID != "free" {
return fmt.Errorf("user is not on free plan")
}
payload := map[string]interface{}{
"userID": userDetails.User.ID,
"expiryTime": expiryTimeInMicro,
"transactionID": fmt.Sprintf("cli-on-%d", time.Now().Unix()),
"productID": "free",
"paymentProvider": "",
"storage": storageInBytes,
}
r, err := c.restClient.R().
SetContext(ctx).
SetResult(&res).
SetBody(payload).
Put("/admin/user/subscription")
if err != nil {
return err
}
if r.IsError() {
return &ApiError{
StatusCode: r.StatusCode(),
Message: r.String(),
}
}
return nil
}

View file

@ -2,19 +2,30 @@ package api
import (
"context"
"github.com/ente-io/cli/utils/constants"
"github.com/spf13/viper"
"strconv"
"strings"
)
var (
downloadHost = "https://files.ente.io/?fileID="
)
func downloadUrl(fileID int64) string {
apiEndpoint := viper.GetString("endpoint.api")
if apiEndpoint == "" || strings.Compare(apiEndpoint, constants.EnteApiUrl) == 0 {
return downloadHost + strconv.FormatInt(fileID, 10)
}
return apiEndpoint + "/files/download/" + strconv.FormatInt(fileID, 10)
}
func (c *Client) DownloadFile(ctx context.Context, fileID int64, absolutePath string) error {
req := c.downloadClient.R().
SetContext(ctx).
SetOutput(absolutePath)
attachToken(req)
r, err := req.Get(downloadHost + strconv.FormatInt(fileID, 10))
r, err := req.Get(downloadUrl(fileID))
if r.IsError() {
return &ApiError{
StatusCode: r.StatusCode(),

View file

@ -30,6 +30,16 @@ func logRequest(req *resty.Request) {
}
}
}
// log query params if present
if len(req.QueryParam) > 0 {
fmt.Println(color.GreenString("Query Params:"))
for k, v := range req.QueryParam {
if k == TokenQuery {
v = []string{"REDACTED"}
}
fmt.Printf("%s: %s\n", color.CyanString(k), color.YellowString(strings.Join(v, ",")))
}
}
}
func logResponse(resp *resty.Response) {

View file

@ -0,0 +1,16 @@
package models
type UserDetails struct {
User struct {
ID int64 `json:"id"`
} `json:"user"`
Usage int64 `json:"usage"`
Email string `json:"email"`
Subscription struct {
ExpiryTime int64 `json:"expiryTime"`
Storage int64 `json:"storage"`
ProductID string `json:"productID"`
PaymentProvider string `json:"paymentProvider"`
} `json:"subscription"`
}

View file

@ -5,11 +5,12 @@ import (
"errors"
"fmt"
"github.com/ente-io/cli/internal/api"
"golang.org/x/term"
"log"
"os"
"regexp"
"strconv"
"strings"
"golang.org/x/term"
)
func GetSensitiveField(label string) (string, error) {
@ -81,6 +82,79 @@ func GetCode(promptText string, length int) (string, error) {
}
}
// parseStorageSize parses a string representing a storage size (e.g., "500MB", "2GB") into bytes.
func parseStorageSize(input string) (int64, error) {
units := map[string]int64{
"MB": 1 << 20,
"GB": 1 << 30,
"TB": 1 << 40,
}
re := regexp.MustCompile(`(?i)^(\d+(?:\.\d+)?)(MB|GB|TB)$`)
matches := re.FindStringSubmatch(input)
if matches == nil {
return 0, errors.New("invalid format")
}
number, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return 0, fmt.Errorf("invalid number: %s", matches[1])
}
unit := strings.ToUpper(matches[2])
bytes := int64(number * float64(units[unit]))
return bytes, nil
}
func ConfirmAction(promptText string) (bool, error) {
for {
input, err := GetUserInput(promptText)
if err != nil {
return false, err
}
if input == "" {
log.Fatal("No input entered")
return false, errors.New("invalid input. Please enter 'y' or 'n'")
}
if input == "c" {
return false, errors.New("cancelled")
}
if input == "y" {
return true, nil
}
if input == "n" {
return false, nil
}
fmt.Println("Invalid input. Please enter 'y' or 'n'.")
}
}
// GetStorageSize prompts the user for a storage size and returns the size in bytes.
func GetStorageSize(promptText string) (int64, error) {
for {
input, err := GetUserInput(promptText)
if err != nil {
return 0, err
}
if input == "" {
log.Fatal("No storage size entered")
return 0, errors.New("no storage size entered")
}
if input == "c" {
return 0, errors.New("storage size entry cancelled")
}
bytes, err := parseStorageSize(input)
if err != nil {
fmt.Println("Invalid storage size format. Please use a valid format like '500MB', '2GB'.")
continue
}
return bytes, nil
}
}
func GetExportDir() string {
for {
exportDir, err := GetUserInput("Enter export directory")

View file

@ -8,12 +8,15 @@ import (
"github.com/ente-io/cli/pkg"
"github.com/ente-io/cli/pkg/secrets"
"github.com/ente-io/cli/utils/constants"
"github.com/spf13/viper"
"log"
"os"
"path/filepath"
"strings"
)
var AppVersion = "0.1.12"
func main() {
cliDBPath, err := GetCLIConfigPath()
if secrets.IsRunningInContainer() {
@ -23,10 +26,10 @@ func main() {
log.Fatalf("Please mount a volume to %s to persist cli data\n%v\n", cliDBPath, err)
}
}
if err != nil {
log.Fatalf("Could not create cli config path\n%v\n", err)
}
initConfig(cliDBPath)
newCliPath := fmt.Sprintf("%s/ente-cli.db", cliDBPath)
if !strings.HasPrefix(cliDBPath, "/") {
oldCliPath := fmt.Sprintf("%sente-cli.db", cliDBPath)
@ -48,8 +51,8 @@ func main() {
}
ctrl := pkg.ClICtrl{
Client: api.NewClient(api.Params{
Debug: false,
//Host: "http://localhost:8080",
Debug: viper.GetBool("log.http"),
Host: viper.GetString("endpoint.api"),
}),
DB: db,
KeyHolder: secrets.NewKeyHolder(secrets.GetOrCreateClISecret()),
@ -63,7 +66,32 @@ func main() {
panic(err)
}
}()
cmd.Execute(&ctrl)
if len(os.Args) == 2 && os.Args[1] == "docs" {
log.Println("Generating docs")
err = cmd.GenerateDocs()
if err != nil {
log.Fatal(err)
}
return
}
cmd.Execute(&ctrl, AppVersion)
}
func initConfig(cliConfigPath string) {
viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath(cliConfigPath + "/") // path to look for the config file in
viper.AddConfigPath(".") // optionally look for config in the working directory
viper.SetDefault("endpoint.api", constants.EnteApiUrl)
viper.SetDefault("log.http", false)
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
} else {
// Config file was found but another error was produced
}
}
}
// GetCLIConfigPath returns the path to the .ente-cli folder and creates it if it doesn't exist.

View file

@ -142,7 +142,7 @@ func (c *ClICtrl) ListAccounts(cxt context.Context) error {
return nil
}
func (c *ClICtrl) UpdateAccount(ctx context.Context, params model.UpdateAccountParams) error {
func (c *ClICtrl) UpdateAccount(ctx context.Context, params model.AccountCommandParams) error {
accounts, err := c.GetAccounts(ctx)
if err != nil {
return err
@ -177,5 +177,27 @@ func (c *ClICtrl) UpdateAccount(ctx context.Context, params model.UpdateAccountP
return b.Put([]byte(accountKey), accInfoBytes)
})
return err
}
func (c *ClICtrl) GetToken(ctx context.Context, params model.AccountCommandParams) error {
accounts, err := c.GetAccounts(ctx)
if err != nil {
return err
}
var acc *model.Account
for _, a := range accounts {
if a.Email == params.Email && a.App == params.App {
acc = &a
break
}
}
if acc == nil {
return fmt.Errorf("account not found, use `account list` to list accounts")
}
secretInfo, err := c.KeyHolder.LoadSecrets(*acc)
if err != nil {
return err
}
fmt.Println(secretInfo.TokenStr())
return nil
}

114
cli/pkg/admin_actions.go Normal file
View file

@ -0,0 +1,114 @@
package pkg
import (
"context"
"fmt"
"github.com/ente-io/cli/internal"
"github.com/ente-io/cli/pkg/model"
"github.com/ente-io/cli/utils"
"log"
"strings"
"time"
)
func (c *ClICtrl) GetUserId(ctx context.Context, params model.AdminActionForUser) error {
accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail)
if err != nil {
return err
}
id, err := c.Client.GetUserIdFromEmail(accountCtx, params.UserEmail)
if err != nil {
return err
}
fmt.Println(id.User.ID)
return nil
}
func (c *ClICtrl) UpdateFreeStorage(ctx context.Context, params model.AdminActionForUser, noLimit bool) error {
accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail)
if err != nil {
return err
}
userDetails, err := c.Client.GetUserIdFromEmail(accountCtx, params.UserEmail)
if err != nil {
return err
}
if noLimit {
// set storage to 100TB and expiry to + 100 years
err := c.Client.UpdateFreePlanSub(accountCtx, userDetails, 100*1024*1024*1024*1024, time.Now().AddDate(100, 0, 0).UnixMicro())
if err != nil {
return err
} else {
fmt.Println("Successfully updated storage and expiry date for user")
}
return nil
}
storageSize, err := internal.GetStorageSize("Enter a storage size (e.g.'5MB', '10GB', '2Tb'): ")
if err != nil {
log.Fatalf("Error: %v", err)
}
dateStr, err := internal.GetUserInput("Enter sub expiry date in YYYY-MM-DD format (e.g.'2040-12-31')")
if err != nil {
log.Fatalf("Error: %v", err)
}
date, err := _parseDateOrDateTime(dateStr)
if err != nil {
return err
}
fmt.Printf("Updating storage for user %s to %s (old %s) with new expirty %s (old %s) \n",
params.UserEmail,
utils.ByteCountDecimalGIB(storageSize), utils.ByteCountDecimalGIB(userDetails.Subscription.Storage),
date.Format("2006-01-02"),
time.UnixMicro(userDetails.Subscription.ExpiryTime).Format("2006-01-02"))
// press y to confirm
confirmed, _ := internal.ConfirmAction("Are you sure you want to update the storage ('y' or 'n')?")
if !confirmed {
return nil
} else {
err := c.Client.UpdateFreePlanSub(accountCtx, userDetails, storageSize, date.UnixMicro())
if err != nil {
return err
} else {
fmt.Println("Successfully updated storage and expiry date for user")
}
}
return nil
}
func (c *ClICtrl) buildAdminContext(ctx context.Context, adminEmail string) (context.Context, error) {
accounts, err := c.GetAccounts(ctx)
if err != nil {
return nil, err
}
var acc *model.Account
for _, a := range accounts {
if a.Email == adminEmail {
acc = &a
break
}
}
if acc == nil {
return nil, fmt.Errorf("account not found for %s, use `account list` to list accounts", adminEmail)
}
secretInfo, err := c.KeyHolder.LoadSecrets(*acc)
if err != nil {
return nil, err
}
accountCtx := c.buildRequestContext(ctx, *acc)
c.Client.AddToken(acc.AccountKey(), secretInfo.TokenStr())
return accountCtx, nil
}
func _parseDateOrDateTime(input string) (time.Time, error) {
var layout string
if strings.Contains(input, " ") {
// If the input contains a space, assume it's a date-time format
layout = "2006-01-02 15:04:05"
} else {
// If there's no space, assume it's just a date
layout = "2006-01-02"
}
return time.Parse(layout, input)
}

View file

@ -1,6 +1,7 @@
package model
import (
"encoding/base64"
"fmt"
"github.com/ente-io/cli/internal/api"
)
@ -17,7 +18,7 @@ type Account struct {
ExportDir string `json:"exportDir"`
}
type UpdateAccountParams struct {
type AccountCommandParams struct {
Email string
App api.App
ExportDir *string
@ -37,3 +38,7 @@ type AccSecretInfo struct {
Token []byte
PublicKey []byte
}
func (a *AccSecretInfo) TokenStr() string {
return base64.URLEncoding.EncodeToString(a.Token)
}

6
cli/pkg/model/admin.go Normal file
View file

@ -0,0 +1,6 @@
package model
type AdminActionForUser struct {
UserEmail string
AdminEmail string
}

View file

@ -1,5 +1,16 @@
#!/bin/bash
# Fetch the latest tag that starts with "cli-"
# shellcheck disable=SC2046
# shellcheck disable=SC2006
LATEST_TAG=$(git describe --tags `git rev-list --tags='cli-*' --max-count=1`)
# Check if the LATEST_TAG variable is empty
if [ -z "$LATEST_TAG" ]; then
echo "No 'cli-' tag found. Exiting..."
exit 1
fi
VERSION=${LATEST_TAG#cli-}
# Create a "bin" directory if it doesn't exist
mkdir -p bin
@ -29,7 +40,7 @@ do
fi
# Build the binary and place it in the "bin" directory
go build -ldflags="-s -w" -trimpath -o "bin/$BINARY_NAME" main.go
go build -ldflags="-X main.AppVersion=${VERSION} -s -w" -trimpath -o "bin/$BINARY_NAME" main.go
# Print a message indicating the build is complete for the current OS and architecture
echo "Built for $OS ($ARCH) as bin/$BINARY_NAME"

View file

@ -1,3 +1,4 @@
package constants
const CliDataPath = "/cli-data/"
const EnteApiUrl = "https://api.ente.io"

31
cli/utils/convert.go Normal file
View file

@ -0,0 +1,31 @@
package utils
import (
"fmt"
)
func ByteCountDecimal(b int64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}
func ByteCountDecimalGIB(b int64) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}

View file

@ -1,7 +1,6 @@
package utils
import (
"fmt"
"log"
"time"
)
@ -10,16 +9,3 @@ func TimeTrack(start time.Time, name string) {
elapsed := time.Since(start)
log.Printf("%s took %s", name, elapsed)
}
func ByteCountDecimal(b int64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}

View file

@ -52,7 +52,4 @@ jobs:
# macOS notarization API key
API_KEY_ID: ${{ secrets.api_key_id }}
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id}}
# setry crash reporting token
SENTRY_AUTH_TOKEN: ${{secrets.sentry_auth_token}}
NEXT_PUBLIC_DISABLE_SENTRY: ${{secrets.next_public_disable_sentry}}
USE_HARD_LINKS: false

29
desktop/.gitignore vendored
View file

@ -1,12 +1,21 @@
node_modules
app
.next/
dist
.vscode
buildingSteps.md
# Node
node_modules/
# macOS
.DS_Store
.idea/
build/.DS_Store
# Editors
.vscode/
# Local env files
.env
.electron-symbols/
models/
.env.*.local
# tsc transpiles src/**/*.ts and emits the generated JS into app
app/
# out is a symlink to the photos web app's dir
out
# electron-builder
dist/

View file

@ -1,11 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
branch="$(git rev-parse --abbrev-ref HEAD)"
if [ "$branch" = "main" ]; then
echo "You can't commit directly to main branch"
exit 1
fi
npx lint-staged

View file

@ -1,6 +1,7 @@
{
"tabWidth": 4,
"trailingComma": "es5",
"singleQuote": true,
"bracketSameLine": true
}
"plugins": [
"prettier-plugin-organize-imports",
"prettier-plugin-packagejson"
]
}

View file

@ -1 +0,0 @@
network-timeout 500000

View file

@ -4,128 +4,128 @@
### New
- Option to select file download location.
- Add support for searching popular cities
- Sorted duplicates in desecending order of size
- Add Counter to upload section
- Display full name and collection name on hover on dedupe screen photos
- Option to select file download location.
- Add support for searching popular cities
- Sorted duplicates in desecending order of size
- Add Counter to upload section
- Display full name and collection name on hover on dedupe screen photos
### Bug Fixes
- Fix add to album padding issue
- Fix double uncategorized album issue
- Hide Hidden collection files from all section
- Fix add to album padding issue
- Fix double uncategorized album issue
- Hide Hidden collection files from all section
## v1.6.62
### New
- Integrated onnx clip runner
- Integrated onnx clip runner
### Bug Fixes
- Fixes login button requiring double click issue
- Fixes Collection sort state not preserved issue
- Fixes continuous export causing app crash
- Improves ML related copies for better distinction from clip
- Added Better favicon for light mode
- Fixed face indexing issues
- Fixed thumbnail load issue
- Fixes login button requiring double click issue
- Fixes Collection sort state not preserved issue
- Fixes continuous export causing app crash
- Improves ML related copies for better distinction from clip
- Added Better favicon for light mode
- Fixed face indexing issues
- Fixed thumbnail load issue
## v1.6.60
### Bug Fixes
- Fix Thumbnail Orientation issue
- Fix ML logging issue
- Fix Thumbnail Orientation issue
- Fix ML logging issue
## v1.6.59
### New
- Added arm64 builds for linux
- Added arm64 builds for linux
### Bug Fixes
- Fix Editor file not loading issue
- Fix ML results missing thumbnail issue
- Fix Editor file not loading issue
- Fix ML results missing thumbnail issue
## v1.6.58
### Bug Fixes
- Fix File load issue
- Fix File load issue
## v1.6.57
### New Features
- Added encrypted Disk caching for files
- Added option to customize cache folder location
- Added encrypted Disk caching for files
- Added option to customize cache folder location
### Bug Fixes
- Fixed caching issue,causing multiple download of file during ml sync
- Fixed caching issue,causing multiple download of file during ml sync
## v1.6.55
### Bug Fixes
- Added manage family portal option if add-on is active
- Fixed filename date parsing issue
- Fixed storage limit ui glitch
- Fixed dedupe page layout issue
- Fixed ElectronAPI refactoring issue
- Fixed Search related issues
- Added manage family portal option if add-on is active
- Fixed filename date parsing issue
- Fixed storage limit ui glitch
- Fixed dedupe page layout issue
- Fixed ElectronAPI refactoring issue
- Fixed Search related issues
## v1.6.54
### New Features
- Added support for HEIC and raw image in photo editor
- Added support for HEIC and raw image in photo editor
### Bug Fixes
- Fixed 16bit HDR HEIC images support
- Fixed blocked login due safe storage issue
- Fixed Search related issues
- Fixed issue of watch folder not cleared on logout
- other under the hood ui/ux improvements
- Fixed 16bit HDR HEIC images support
- Fixed blocked login due safe storage issue
- Fixed Search related issues
- Fixed issue of watch folder not cleared on logout
- other under the hood ui/ux improvements
## v1.6.53
### Bug Fixes
- Fixed watch folder disabled issue
- Fixed BF Add on related issues
- Fixed clip sync issue and added better logging
- Fixed mov file upload
- Fixed clip extraction related issue
- Fixed watch folder disabled issue
- Fixed BF Add on related issues
- Fixed clip sync issue and added better logging
- Fixed mov file upload
- Fixed clip extraction related issue
## v1.6.52
### New Features
- Added Clip Desktop on windows
- Added Clip Desktop on windows
### Bug Fixes
- fixed google json matching issue
- other under-the-hood changes to improve performance and bug fixes
- fixed google json matching issue
- other under-the-hood changes to improve performance and bug fixes
## v1.6.50
### New Features
- Added Clip desktop
- Added Clip desktop
### Bug Fixes
- Fixed desktop downloaded file had extra dot in the name
- Cleanup error messages
- fix the motion photo clustering issue
- Add option to disable cf proxy locally
- other under-the-hood changes to improve UX
- Fixed desktop downloaded file had extra dot in the name
- Cleanup error messages
- fix the motion photo clustering issue
- Add option to disable cf proxy locally
- other under-the-hood changes to improve UX
## v1.6.49
@ -137,54 +137,54 @@ Check out our [blog](https://ente.io/blog/introducing-web-desktop-photo-editor/)
### Bug Fixes
- Fixed misaligned icons in photo-viewer
- Fixed issue with Motion photo upload
- Fixed issue with Live-photo upload
- other minor ux improvement
- Fixed misaligned icons in photo-viewer
- Fixed issue with Motion photo upload
- Fixed issue with Live-photo upload
- other minor ux improvement
## v1.6.46
### Bug Fixes
- Fixes OOM crashes during file upload [#1379](https://github.com/ente-io/photos-web/pull/1379)
- Fixes OOM crashes during file upload [#1379](https://github.com/ente-io/photos-web/pull/1379)
## v1.6.45
### Bug Fixes
- Fixed app keeps reloading issue [#235](https://github.com/ente-io/photos-desktop/pull/235)
- Fixed dng and arw preview issue [#1378](https://github.com/ente-io/photos-web/pull/1378)
- Added view crash report option (help menu) for user to share electron crash report locally
- Fixed app keeps reloading issue [#235](https://github.com/ente-io/photos-desktop/pull/235)
- Fixed dng and arw preview issue [#1378](https://github.com/ente-io/photos-web/pull/1378)
- Added view crash report option (help menu) for user to share electron crash report locally
## v1.6.44
- Upgraded electron to get latest security patches and other improvements.
- Upgraded electron to get latest security patches and other improvements.
## v1.6.43
### Added
- #### Check for update and changelog option
- #### Check for update and changelog option
Added options to check for update manually and a view changelog via the app menubar
- #### Opt out of crash reporting
- #### Opt out of crash reporting
Added option to out of a crash reporting, it can accessed from the settings -> preferences -> disable crash reporting
- #### Type search
- #### Type search
Added new search option to search files based on file type i.e, image, video, live-photo.
- #### Manual Convert Button
- #### Manual Convert Button
In case the video is not playable, Now there is a convert button which can be used to trigger conversion of the video to supported format.
- #### File Download Progress
- #### File Download Progress
The file loader now also shows the exact percentage download progress, instead of just a simple loader.
- #### Bug fixes & other enhancements
- #### Bug fixes & other enhancements
We have squashed a few pesky bugs that were reported by our community
@ -192,21 +192,21 @@ Check out our [blog](https://ente.io/blog/introducing-web-desktop-photo-editor/)
### Added
- #### Hidden albums
- #### Hidden albums
You can now hide albums, just like individual memories.
- #### Email verification
- #### Email verification
We have now made email verification optional, so you can sign in with just your email address and password, without waiting for a verification code.
You can opt in / out of email verification from Settings > Security.
- #### Download Album
- #### Download Album
You can now chose the download location for downloading albums. Along with that we have also added progress bar for album download.
- #### Bug fixes & other enhancements
- #### Bug fixes & other enhancements
We have squashed a few pesky bugs that were reported by our community

View file

@ -10,33 +10,28 @@ To know more about Ente, see [our main README](../README.md) or visit
## Building from source
> [!CAUTION]
>
> We moved a few things around when switching to a monorepo recently, so this
> folder might not build with the instructions below. Hang tight, we're on it,
> will fix things if.
Fetch submodules
```sh
git submodule update --init --recursive
```
Install dependencies
```sh
yarn install
```
Run the app
Run in development mode (with hot reload)
```sh
yarn start
yarn dev
```
To recompile automatically using electron-reload, run this in a separate
terminal:
> [!CAUTION]
>
> `yarn dev` is currently not working (we'll fix soon). If you just want to
> build from source and use the generated binary, use `yarn build`.
```bash
yarn watch
Or create a binary for your platform
```sh
yarn build
```
That's the gist of it. For more development related documentation, see
[docs](docs/README.md).

View file

@ -1,20 +1,30 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ente Photos</title>
</head>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ente Photos</title>
</head>
<body style="background-color: black;">
<div style=" height: 95vh;width: 96vw; display: grid; place-items: center; color: white;">
<div>
<div style="margin-bottom: 10px;">Site unreachable, please try again later</div>
<button onClick="window[`ElectronAPIs`].reloadWindow()">Reload</button>
<body style="background-color: black">
<div
style="
height: 95vh;
width: 96vw;
display: grid;
place-items: center;
color: white;
"
>
<div>
<div style="margin-bottom: 10px">
Site unreachable, please try again later
</div>
<button onClick="window[`ElectronAPIs`].reloadWindow()">
Reload
</button>
</div>
</div>
</div>
</body>
</body>
</html>

View file

@ -1,30 +1,50 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ente Photos</title>
</head>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ente Photos</title>
</head>
<body style="background-color: black;">
<div style="display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;">
<div style="width:64px;"><svg version="1.1" id="L9" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100"
enable-background="new 0 0 0 0" xml:space="preserve">
<path fill="#2dc262"
d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
<animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s"
from="0 50 50" to="360 50 50" repeatCount="indefinite" />
</path>
</svg>
<body style="background-color: black">
<div
style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;
"
>
<div style="width: 64px">
<svg
version="1.1"
id="L9"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 100 100"
enable-background="new 0 0 0 0"
xml:space="preserve"
>
<path
fill="#2dc262"
d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50"
>
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="1s"
from="0 50 50"
to="360 50 50"
repeatCount="indefinite"
/>
</path>
</svg>
</div>
</div>
</div>
</body>
</body>
</html>

View file

@ -1,24 +1,24 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<title>Electron Updater Example</title>
</head>
<body>
Current version: <span id="version">vX.Y.Z</span>
<div id="messages"></div>
<script>
// Display the current version
let version = window.location.hash.substring(1);
document.getElementById('version').innerText = version;
<head>
<title>Electron Updater Example</title>
</head>
<body>
Current version: <span id="version">vX.Y.Z</span>
<div id="messages"></div>
<script>
// Display the current version
let version = window.location.hash.substring(1);
document.getElementById("version").innerText = version;
// Listen for messages
const {ipcRenderer} = require('electron');
ipcRenderer.on('message', function(event, text) {
var container = document.getElementById('messages');
var message = document.createElement('div');
message.innerHTML = text;
container.appendChild(message);
})
</script>
</body>
</html>
// Listen for messages
const { ipcRenderer } = require("electron");
ipcRenderer.on("message", function (event, text) {
var container = document.getElementById("messages");
var message = document.createElement("div");
message.innerHTML = text;
container.appendChild(message);
});
</script>
</body>
</html>

View file

@ -1,25 +0,0 @@
Notes on how to upload electron symbols directly to sentry instance (bypassing the CF limits) cc @abhi just for future reference
To upload electron symbols
1. Create a tunnel
```
ssh -p 7426 -N -L 8080:localhost:9000 sentry
```
2. Add the following env file
```
NEXT_PUBLIC_IS_SENTRY_ENABLED = yes
SENTRY_ORG = ente
SENTRY_PROJECT = bhari-frame
SENTRY_URL2 = https://sentry.ente.io/
SENTRY_URL = http://localhost:8080/
SENTRY_AUTH_TOKEN = xxx
SENTRY_LOG_LEVEL = debug
```
3. Run
```
node sentry-symbols.js
```

11
desktop/docs/README.md Normal file
View file

@ -0,0 +1,11 @@
# Developer docs
If you just want to run the Ente Photos desktop app locally or develop it, you
can do:
yarn install
yarn dev
The docs in this directory provide more details that some developers might find
useful. You might also find the developer docs for
[web](../../web/docs/README.md) useful.

View file

@ -0,0 +1,14 @@
# Dependencies
See [web/docs/dependencies.md](../../web/docs/dependencies.md) for general web
specific dependencies. See [electron.md](electron.md) for our main dependency,
Electron. The rest of this document describes the remaining, desktop specific
dependencies that are used by the Photos desktop app.
## Electron related
### next-electron-server
This spins up a server for serving files using a protocol handler inside our
Electron process. This allows us to directly use the output produced by `next
build` for loading into our renderer process.

4
desktop/docs/dev.md Normal file
View file

@ -0,0 +1,4 @@
# Development tips
- `yarn build:quick` is a variant of `yarn build` that uses the
`--config.compression=store` flag to (slightly) speed up electron-builder.

21
desktop/docs/electron.md Normal file
View file

@ -0,0 +1,21 @@
# Electron
[Electron](https://www.electronjs.org) is a cross-platform (Linux, Windows,
macOS) way for creating desktop apps using TypeScript.
Electron embeds Chromium and Node.js in the generated app's binary. The
generated app thus consists of two separate processes - the _main_ process, and
a _renderer_ process.
- The _main_ process is runs the embedded node. This process can deal with the
host OS - it is conceptually like a `node` repl running on your machine. In our
case, the TypeScript code (in the `src/` directory) gets transpiled by `tsc`
into JavaScript in the `build/app/` directory, which gets bundled in the
generated app's binary and is loaded by the node (main) process when the app
starts.
- The _renderer_ process is a regular web app that gets loaded into the embedded
Chromium. When the main process starts, it creates a new "window" that shows
this embedded Chromium. In our case, we build and bundle a static export of
the [Photos web app](../web/README.md) in the generated app. This gets loaded
by the embedded Chromium at runtime, acting as the app's UI.

View file

@ -1,10 +1,65 @@
{
"name": "ente",
"productName": "ente",
"version": "1.6.63",
"private": true,
"description": "Desktop client for ente.io",
"description": "Desktop client for Ente Photos",
"author": "Ente <code@ente.io>",
"main": "app/main.js",
"scripts": {
"build": "yarn build-renderer && yarn build-main",
"build-main": "tsc && electron-builder",
"build-main:quick": "tsc && electron-builder --config.compression=store",
"build-renderer": "cd ../web && yarn install && yarn build:photos && cd ../desktop && rm -f out && ln -sf ../web/apps/photos/out",
"build:quick": "yarn build-renderer && yarn build-main:quick",
"dev": "concurrently --names 'main,rndr,tscw' \"yarn dev-main\" \"yarn dev-renderer\" \"yarn dev-main-watch\"",
"dev-main": "tsc && electron app/main.js",
"dev-main-watch": "tsc --watch --preserveWatchOutput",
"dev-renderer": "cd ../web && yarn install && yarn dev:photos",
"postinstall": "electron-builder install-app-deps",
"lint": "yarn prettier --check . && eslint \"src/**/*.ts\"",
"lint-fix": "yarn prettier --write . && eslint --fix src"
},
"dependencies": {
"any-shell-escape": "^0.1.1",
"auto-launch": "^5.0.5",
"chokidar": "^3.5.3",
"compare-versions": "^6.1.0",
"electron-log": "^4.3.5",
"electron-reload": "^2.0.0-alpha.1",
"electron-store": "^8.0.1",
"electron-updater": "^4.3.8",
"ffmpeg-static": "^5.1.0",
"get-folder-size": "^2.0.1",
"html-entities": "^2.4.0",
"jpeg-js": "^0.4.4",
"next-electron-server": "^1",
"node-fetch": "^2.6.7",
"node-stream-zip": "^1.15.0",
"onnxruntime-node": "^1.16.3",
"promise-fs": "^2.1.1"
},
"devDependencies": {
"@types/auto-launch": "^5.0.2",
"@types/ffmpeg-static": "^3.0.1",
"@types/get-folder-size": "^2.0.0",
"@types/node": "18.15.0",
"@types/node-fetch": "^2.6.2",
"@types/promise-fs": "^2.1.1",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"concurrently": "^7.0.0",
"electron": "^25.8.4",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.2.0",
"electron-download": "^4.1.1",
"eslint": "^7.23.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^3",
"prettier-plugin-organize-imports": "^3.2",
"prettier-plugin-packagejson": "^2.4",
"typescript": "^4.2.3"
},
"build": {
"appId": "io.ente.bhari-frame",
"artifactName": "${productName}-${version}-${arch}.${ext}",
@ -42,7 +97,7 @@
]
}
],
"icon": "./build/icon.icns",
"icon": "./resources/icon.icns",
"category": "Photography"
},
"mac": {
@ -57,98 +112,24 @@
"x64ArchFiles": "Contents/Resources/ggmlclip-mac"
},
"afterSign": "electron-builder-notarize",
"extraFiles": [
{
"from": "build",
"to": "resources",
"filter": [
"**/*"
]
}
],
"asarUnpack": [
"node_modules/ffmpeg-static/bin/${os}/${arch}/ffmpeg",
"node_modules/ffmpeg-static/index.js",
"node_modules/ffmpeg-static/package.json"
],
"extraFiles": [
{
"from": "build",
"to": "resources"
}
],
"files": [
"app/**/*",
{
"from": "ui/apps/photos",
"to": "ui",
"filter": [
"!**/*",
"out/**/*"
]
}
"out"
]
},
"scripts": {
"postinstall": "electron-builder install-app-deps",
"prebuild": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"prepare": "husky install",
"lint": "eslint -c .eslintrc --ext .ts src",
"watch": "tsc -w",
"build-main": "yarn install && tsc",
"start-main": "yarn build-main && electron app/main.js",
"start-renderer": "cd ui && yarn install && yarn dev:photos",
"start": "concurrently \"yarn start-main\" \"yarn start-renderer\"",
"build-renderer": "cd ui && yarn install && yarn export:photos",
"build": "yarn build-renderer && yarn build-main",
"test-release": "cross-env IS_TEST_RELEASE=true yarn build && electron-builder --config.compression=store"
},
"author": "ente <code@ente.io>",
"devDependencies": {
"@sentry/cli": "^1.68.0",
"@types/auto-launch": "^5.0.2",
"@types/ffmpeg-static": "^3.0.1",
"@types/get-folder-size": "^2.0.0",
"@types/node": "18.15.0",
"@types/node-fetch": "^2.6.2",
"@types/promise-fs": "^2.1.1",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"concurrently": "^7.0.0",
"cross-env": "^7.0.3",
"electron": "^25.8.4",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.2.0",
"electron-download": "^4.1.1",
"eslint": "^7.23.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^8.5.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.1",
"prettier": "2.5.1",
"typescript": "^4.2.3"
},
"dependencies": {
"@sentry/electron": "^2.5.1",
"any-shell-escape": "^0.1.1",
"auto-launch": "^5.0.5",
"chokidar": "^3.5.3",
"compare-versions": "^6.1.0",
"electron-log": "^4.3.5",
"electron-reload": "^2.0.0-alpha.1",
"electron-store": "^8.0.1",
"electron-updater": "^4.3.8",
"ffmpeg-static": "^5.1.0",
"get-folder-size": "^2.0.1",
"html-entities": "^2.4.0",
"jpeg-js": "^0.4.4",
"next-electron-server": "file:./thirdparty/next-electron-server",
"node-fetch": "^2.6.7",
"node-stream-zip": "^1.15.0",
"onnxruntime-node": "^1.16.3",
"promise-fs": "^2.1.1"
},
"productName": "ente",
"standard": {
"parser": "babel-eslint"
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write --ignore-unknown"
]
}
}

View file

@ -1,94 +0,0 @@
#!/usr/bin/env node
let SentryCli;
let download;
try {
SentryCli = require('@sentry/cli');
download = require('electron-download');
} catch (e) {
console.error('ERROR: Missing required packages, please run:');
console.error('npm install --save-dev @sentry/cli electron-download');
process.exit(1);
}
const SYMBOL_CACHE_FOLDER = '.electron-symbols';
const sentryCli = new SentryCli('./sentry.properties');
async function main() {
const version = getElectronVersion();
if (!version) {
console.error('Cannot detect electron version, check that electron is installed');
return;
}
console.log('We are starting to download all possible electron symbols');
console.log('We need it in order to symbolicate native crashes');
console.log(
'This step is only needed once whenever you update your electron version',
);
console.log('Just call this script again it should do everything for you.');
let zipPath = await downloadSymbols({
version,
platform: 'darwin',
arch: 'x64',
dsym: true,
});
await sentryCli.execute(['upload-dif', '-t', 'dsym', zipPath], true);
zipPath = await downloadSymbols({
version,
platform: 'win32',
arch: 'ia32',
symbols: true,
});
await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true);
zipPath = await downloadSymbols({
version,
platform: 'win32',
arch: 'x64',
symbols: true,
});
await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true);
zipPath = await downloadSymbols({
version,
platform: 'linux',
arch: 'x64',
symbols: true,
});
await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true);
console.log('Finished downloading and uploading to Sentry');
console.log(`Feel free to delete the ${SYMBOL_CACHE_FOLDER}`);
}
function getElectronVersion() {
try {
return require('electron/package.json').version;
} catch (error) {
return undefined;
}
}
async function downloadSymbols(options) {
return new Promise((resolve, reject) => {
download(
{
...options,
cache: SYMBOL_CACHE_FOLDER,
},
(err, zipPath) => {
if (err) {
reject(err);
} else {
resolve(zipPath);
}
},
);
});
}
main().catch(e => console.error(e));

View file

@ -1,3 +0,0 @@
defaults.url=https://sentry.ente.io/
defaults.org=ente
defaults.project=desktop-photos

View file

@ -1,16 +1,16 @@
import { ipcRenderer } from 'electron/renderer';
import path from 'path';
import { existsSync, mkdir, rmSync } from 'promise-fs';
import { DiskCache } from '../services/diskCache';
import { ipcRenderer } from "electron/renderer";
import path from "path";
import { existsSync, mkdir, rmSync } from "promise-fs";
import { DiskCache } from "../services/diskCache";
const ENTE_CACHE_DIR_NAME = 'ente';
const ENTE_CACHE_DIR_NAME = "ente";
export const getCacheDirectory = async () => {
const customCacheDir = await getCustomCacheDirectory();
if (customCacheDir && existsSync(customCacheDir)) {
return customCacheDir;
}
const defaultSystemCacheDir = await ipcRenderer.invoke('get-path', 'cache');
const defaultSystemCacheDir = await ipcRenderer.invoke("get-path", "cache");
return path.join(defaultSystemCacheDir, ENTE_CACHE_DIR_NAME);
};
@ -22,7 +22,7 @@ const getCacheBucketDir = async (cacheName: string) => {
export async function openDiskCache(
cacheName: string,
cacheLimitInBytes?: number
cacheLimitInBytes?: number,
) {
const cacheBucketDir = await getCacheBucketDir(cacheName);
if (!existsSync(cacheBucketDir)) {
@ -42,11 +42,11 @@ export async function deleteDiskCache(cacheName: string) {
}
export async function setCustomCacheDirectory(
directory: string
directory: string,
): Promise<void> {
await ipcRenderer.invoke('set-custom-cache-directory', directory);
await ipcRenderer.invoke("set-custom-cache-directory", directory);
}
async function getCustomCacheDirectory(): Promise<string> {
return await ipcRenderer.invoke('get-custom-cache-directory');
return await ipcRenderer.invoke("get-custom-cache-directory");
}

View file

@ -1,22 +1,21 @@
import { ipcRenderer } from 'electron';
import { writeStream } from '../services/fs';
import { isExecError } from '../utils/error';
import { parseExecError } from '../utils/error';
import { Model } from '../types';
import { ipcRenderer } from "electron";
import { writeStream } from "../services/fs";
import { Model } from "../types";
import { isExecError, parseExecError } from "../utils/error";
export async function computeImageEmbedding(
model: Model,
imageData: Uint8Array
imageData: Uint8Array,
): Promise<Float32Array> {
let tempInputFilePath = null;
try {
tempInputFilePath = await ipcRenderer.invoke('get-temp-file-path', '');
tempInputFilePath = await ipcRenderer.invoke("get-temp-file-path", "");
const imageStream = new Response(imageData.buffer).body;
await writeStream(tempInputFilePath, imageStream);
const embedding = await ipcRenderer.invoke(
'compute-image-embedding',
"compute-image-embedding",
model,
tempInputFilePath
tempInputFilePath,
);
return embedding;
} catch (err) {
@ -28,20 +27,20 @@ export async function computeImageEmbedding(
}
} finally {
if (tempInputFilePath) {
await ipcRenderer.invoke('remove-temp-file', tempInputFilePath);
await ipcRenderer.invoke("remove-temp-file", tempInputFilePath);
}
}
}
export async function computeTextEmbedding(
model: Model,
text: string
text: string,
): Promise<Float32Array> {
try {
const embedding = await ipcRenderer.invoke(
'compute-text-embedding',
"compute-text-embedding",
model,
text
text,
);
return embedding;
} catch (err) {

View file

@ -1,44 +1,39 @@
import { ipcRenderer } from 'electron/renderer';
import { logError } from '../services/logging';
import { ipcRenderer } from "electron/renderer";
import { logError } from "../services/logging";
export const selectDirectory = async (): Promise<string> => {
try {
return await ipcRenderer.invoke('select-dir');
return await ipcRenderer.invoke("select-dir");
} catch (e) {
logError(e, 'error while selecting root directory');
logError(e, "error while selecting root directory");
}
};
export const getAppVersion = async (): Promise<string> => {
try {
return await ipcRenderer.invoke('get-app-version');
return await ipcRenderer.invoke("get-app-version");
} catch (e) {
logError(e, 'failed to get release version');
logError(e, "failed to get release version");
throw e;
}
};
export const openDirectory = async (dirPath: string): Promise<void> => {
try {
await ipcRenderer.invoke('open-dir', dirPath);
await ipcRenderer.invoke("open-dir", dirPath);
} catch (e) {
logError(e, 'error while opening directory');
logError(e, "error while opening directory");
throw e;
}
};
export const getPlatform = async (): Promise<'mac' | 'windows' | 'linux'> => {
export const getPlatform = async (): Promise<"mac" | "windows" | "linux"> => {
try {
return await ipcRenderer.invoke('get-platform');
return await ipcRenderer.invoke("get-platform");
} catch (e) {
logError(e, 'failed to get platform');
logError(e, "failed to get platform");
throw e;
}
};
export {
logToDisk,
openLogDirectory,
getSentryUserID,
updateOptOutOfCrashReports,
} from '../services/logging';
export { logToDisk, openLogDirectory } from "../services/logging";

View file

@ -1,9 +1,8 @@
import { keysStore } from '../stores/keys.store';
import { safeStorageStore } from '../stores/safeStorage.store';
import { uploadStatusStore } from '../stores/upload.store';
import { logError } from '../services/logging';
import { userPreferencesStore } from '../stores/userPreferences.store';
import { watchStore } from '../stores/watch.store';
import { logError } from "../services/logging";
import { keysStore } from "../stores/keys.store";
import { safeStorageStore } from "../stores/safeStorage.store";
import { uploadStatusStore } from "../stores/upload.store";
import { watchStore } from "../stores/watch.store";
export const clearElectronStore = () => {
try {
@ -11,9 +10,8 @@ export const clearElectronStore = () => {
keysStore.clear();
safeStorageStore.clear();
watchStore.clear();
userPreferencesStore.delete('optOutOfCrashReports');
} catch (e) {
logError(e, 'error while clearing electron store');
logError(e, "error while clearing electron store");
throw e;
}
};

Some files were not shown because too many files have changed in this diff Show more