Repository: leinelissen/aeon Branch: master Commit: 53ac8fb259be Files: 184 Total size: 529.3 KB Directory structure: gitextract_uqsqlvwh/ ├── .eslintrc.json ├── .gitbook.yaml ├── .github/ │ └── workflows/ │ ├── build.yml │ ├── codeql-analysis.yml │ ├── electronegativity.yml │ └── release-notes.yml ├── .gitignore ├── .husky/ │ ├── pre-commit │ └── pre-push ├── .npmrc ├── .vscode/ │ ├── debug-launcher.sh │ ├── launch.json │ └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── data/ │ └── .gitignore ├── docs/ │ ├── README.md │ ├── SUMMARY.md │ ├── extending-aeon/ │ │ ├── architecture/ │ │ │ ├── README.md │ │ │ └── providers.md │ │ ├── local-development.md │ │ └── reporting-issues.md │ └── using-aeon/ │ ├── getting-started.md │ └── installation.md ├── entitlements.plist ├── forge.config.js ├── package.json ├── playwright.config.ts ├── renovate.json ├── scripts/ │ ├── prepareNodegit.js │ └── setupMacOSCertificates.sh ├── src/ │ ├── app/ │ │ ├── assets/ │ │ │ └── open-data-rights.ts │ │ ├── components/ │ │ │ ├── App.tsx │ │ │ ├── Button.tsx │ │ │ ├── Code.tsx │ │ │ ├── IconBadge.tsx │ │ │ ├── Input.tsx │ │ │ ├── Loading.tsx │ │ │ ├── Menu.tsx │ │ │ ├── Modal/ │ │ │ │ ├── Menu.tsx │ │ │ │ └── index.tsx │ │ │ ├── NoData.tsx │ │ │ ├── PanelGrid.tsx │ │ │ ├── RightSideOverlay.tsx │ │ │ ├── Timestamp.tsx │ │ │ ├── Tooltip.tsx │ │ │ ├── Tour/ │ │ │ │ ├── index.tsx │ │ │ │ ├── steps.tsx │ │ │ │ └── useTour.tsx │ │ │ ├── Typography.tsx │ │ │ └── Utility.tsx │ │ ├── index.ejs │ │ ├── index.tsx │ │ ├── polyfill.ts │ │ ├── preload.ts │ │ ├── screens/ │ │ │ ├── Accounts/ │ │ │ │ ├── components/ │ │ │ │ │ ├── AccountOverlay.tsx │ │ │ │ │ ├── EmailProvider.tsx │ │ │ │ │ └── NewAccountModal.tsx │ │ │ │ ├── getDescription.ts │ │ │ │ └── index.tsx │ │ │ ├── Data/ │ │ │ │ ├── components/ │ │ │ │ │ └── DatumOverlay.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── styles.tsx │ │ │ │ └── types.tsx │ │ │ ├── Erasure/ │ │ │ │ ├── Emails.tsx │ │ │ │ ├── generateEmail.ts │ │ │ │ └── index.tsx │ │ │ ├── Graph/ │ │ │ │ ├── calculateGraph.ts │ │ │ │ ├── explainer.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── renderNode.ts │ │ │ │ └── style.ts │ │ │ ├── Onboarding/ │ │ │ │ └── index.tsx │ │ │ ├── Settings/ │ │ │ │ ├── email/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── ImapInitialiser.tsx │ │ │ │ │ │ └── NewAccountModal.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── Timeline/ │ │ │ │ ├── components/ │ │ │ │ │ ├── Commit.tsx │ │ │ │ │ └── Diff.tsx │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ ├── store/ │ │ │ ├── accounts/ │ │ │ │ ├── actions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── selectors.ts │ │ │ │ └── types.ts │ │ │ ├── data/ │ │ │ │ ├── actions.ts │ │ │ │ ├── index.ts │ │ │ │ └── selectors.ts │ │ │ ├── email/ │ │ │ │ ├── actions.ts │ │ │ │ ├── index.ts │ │ │ │ └── selectors.ts │ │ │ ├── index.ts │ │ │ ├── migrations.ts │ │ │ ├── new-commits/ │ │ │ │ ├── actions.ts │ │ │ │ └── index.ts │ │ │ ├── onboarding/ │ │ │ │ ├── actions.ts │ │ │ │ └── index.ts │ │ │ ├── persist.ts │ │ │ └── telemetry/ │ │ │ ├── actions.ts │ │ │ └── index.ts │ │ ├── styles/ │ │ │ ├── global.css │ │ │ ├── index.ts │ │ │ ├── snippets.ts │ │ │ └── theme.css │ │ └── utilities/ │ │ ├── DataType.tsx │ │ ├── Email.ts │ │ ├── Providers.ts │ │ ├── Repository.ts │ │ ├── convertMetaToObject.ts │ │ ├── env.ts │ │ ├── isValidUrl.ts │ │ └── usePrevious.ts │ ├── icon.icns │ ├── main/ │ │ ├── email-client/ │ │ │ ├── bridge.ts │ │ │ ├── gmail/ │ │ │ │ ├── index.ts │ │ │ │ ├── oauth.ts │ │ │ │ └── types.ts │ │ │ ├── imap/ │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── outlook/ │ │ │ │ ├── index.ts │ │ │ │ └── oauth.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── initialise.ts │ │ ├── lib/ │ │ │ ├── app-path.ts │ │ │ ├── constants.ts │ │ │ ├── create-secure-window.ts │ │ │ ├── crypto-fs/ │ │ │ │ └── index.ts │ │ │ ├── logger.ts │ │ │ ├── map-map.ts │ │ │ ├── map-object-to-key-value.ts │ │ │ ├── notifications/ │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── oauth.ts │ │ │ ├── object-to-map.ts │ │ │ ├── persisted-map.ts │ │ │ ├── protocol-handler.ts │ │ │ ├── repository/ │ │ │ │ ├── bridge.ts │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── utilities/ │ │ │ │ ├── diff-map.ts │ │ │ │ ├── generate-diff.ts │ │ │ │ ├── generate-parsed-commit.ts │ │ │ │ ├── parse-csv.ts │ │ │ │ ├── parse-open-data-rights.ts │ │ │ │ └── parse-schema.ts │ │ │ ├── unwrap-provider-source.ts │ │ │ └── window-store.ts │ │ ├── providers/ │ │ │ ├── bridge.ts │ │ │ ├── facebook/ │ │ │ │ ├── index.ts │ │ │ │ └── parser.ts │ │ │ ├── index.ts │ │ │ ├── instagram/ │ │ │ │ ├── index.ts │ │ │ │ ├── parser.ts │ │ │ │ └── urls.json │ │ │ ├── linkedin/ │ │ │ │ ├── index.ts │ │ │ │ └── parser.ts │ │ │ ├── open-data-rights/ │ │ │ │ └── index.ts │ │ │ ├── parsers.ts │ │ │ ├── spotify/ │ │ │ │ ├── index.ts │ │ │ │ └── parser.ts │ │ │ └── types/ │ │ │ ├── Data.ts │ │ │ ├── Events.ts │ │ │ ├── Provider.ts │ │ │ └── index.ts │ │ ├── store.ts │ │ └── updates.ts │ └── typings/ │ ├── cytoscape-fcose.d.ts │ ├── fonts.d.ts │ ├── images.d.ts │ └── redux-persist.d.ts ├── test/ │ ├── .gitignore │ ├── spec.ts │ └── utilities/ │ ├── getRandomNode.ts │ └── getRoute.ts ├── tsconfig.json ├── webpack.main.config.js ├── webpack.plugins.js ├── webpack.renderer.config.js └── webpack.rules.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "env": { "browser": true, "es6": true, "node": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:import/errors", "plugin:import/warnings", "plugin:import/typescript", "plugin:react/recommended", "airbnb-typescript", "plugin:jsx-a11y/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { "tsConfigRootDir": "./", "project": "./tsconfig.json" }, "plugins": [ "@typescript-eslint", "import", "deprecation", "jsx-a11y" ], "rules": { "indent": ["error", 4, { "SwitchCase": 1 }], "react/jsx-indent": ["error", 4], "react/jsx-indent-props": ["error", 4], "@typescript-eslint/indent": ["error", 4], "react/prop-types": "off", "arrow-parens": "error", "deprecation/deprecation": "warn" }, "settings": { "import/resolver": { "typescript": {}, // this loads /tsconfig.json to eslint "node": { "paths": [ "src" ] } }, "import/core-modules": [ "electron", "nodegit" ], "react": { "version": "detect" } } } ================================================ FILE: .gitbook.yaml ================================================ root: ./docs/ ================================================ FILE: .github/workflows/build.yml ================================================ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: Build on: [push] jobs: lint: name: 'Lint' runs-on: macos-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 1 submodules: true - name: Setup Node uses: actions/setup-node@v3 with: node-version: '16' - uses: actions/cache@v3 with: path: '**/node_modules' key: linter-node_modules-${{hashFiles('**/package-lock.json') }} - name: Install dependencies env: JOBS: 'max' run: npm install - name: Run linter run: npm run lint build: name: Build (${{ matrix.os }} - ${{ matrix.arch }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: # Build for supported platforms # https://github.com/electron/electron-packager/blob/ebcbd439ff3e0f6f92fa880ff28a8670a9bcf2ab/src/targets.js#L9 include: - os: ubuntu-20.04 arch: arm64 - os: ubuntu-20.04 arch: x64 - os: macOS-11 arch: arm64 openssl_dir: '/tmp/openssl@1.1/1.1.1l_1' - os: macOS-11 arch: x64 openssl_dir: '/usr/local/opt/openssl@1.1' - os: windows-2019 arch: x64 openssl_dir: 'C:\Program Files\OpenSSL-Win64\' # - os: ubuntu-20.04 # arch: armv7l # - os: windows-2019 # arch: arm64 # openssl_dir: 'C:\Program Files\OpenSSL-Win64\' steps: - uses: actions/checkout@v3 with: fetch-depth: 1 submodules: true - name: Setup Node uses: actions/setup-node@v3 with: node-version: '16' - uses: actions/cache@v3 with: path: '**/node_modules' key: ${{ matrix.os }}-${{ matrix.arch }}-node_modules-${{hashFiles('**/package-lock.json') }} - name: Install Build dependencies (macOS-arm64) if: | matrix.os == 'macOS-11' && matrix.arch == 'arm64' run: | curl -L -H "Authorization: Bearer QQ==" -o /tmp/openssl-1.1.1l_1-arm64.tar.gz https://ghcr.io/v2/homebrew/core/openssl/1.1/blobs/sha256:8f5b0bee61c1570b9f0fc0a21d6c322e904ae7975bdaada5787451d18e9677a6 tar -xvf /tmp/openssl-1.1.1l_1-arm64.tar.gz -C /tmp - name: Install Build dependencies (Windows) if: matrix.os == 'windows-2019' run: choco install openssl - name: Install Build dependencies (Ubuntu) if: matrix.os == 'ubuntu-20.04' run: | sudo apt update sudo apt install libkrb5-dev - name: Install dependencies env: JOBS: 'max' npm_config_openssl_dir: ${{ matrix.openssl_dir }} run: | npm install - name: Rebuild native modules env: JOBS: 'max' npm_config_openssl_dir: ${{ matrix.openssl_dir }} run: | npm run rebuild:native-modules -- --arch=${{ matrix.arch }} npm run prepare:nodegit - name: Load Developer Certificates (macOS) if: | startsWith(github.ref, 'refs/tags/') && matrix.os == 'macOS-11' env: MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} run: chmod +x scripts/setupMacOSCertificates.sh && ./scripts/setupMacOSCertificates.sh - name: Compile and Sign if: startsWith(github.ref, 'refs/tags/') env: npm_config_openssl_dir: ${{ matrix.openssl_dir }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} GMAIL_OAUTH_CLIENT_ID: ${{ secrets.GMAIL_OAUTH_CLIENT_ID }} GMAIL_OAUTH_CLIENT_SECRET: ${{ secrets.GMAIL_OAUTH_CLIENT_SECRET }} run: npm run make -- --arch=${{ matrix.arch }} - name: Compile if: startsWith(github.ref, 'refs/tags/') == false env: npm_config_openssl_dir: ${{ matrix.openssl_dir }} GMAIL_OAUTH_CLIENT_ID: ${{ secrets.GMAIL_OAUTH_CLIENT_ID }} GMAIL_OAUTH_CLIENT_SECRET: ${{ secrets.GMAIL_OAUTH_CLIENT_SECRET }} run: npm run make -- --arch=${{ matrix.arch }} - name: Test if: | matrix.os != 'ubuntu-20.04' && matrix.arch == 'x64' && matrix.os != 'macOS-11' && matrix.os != 'windows-2019' run: npm run test - name: Test (Ubuntu) if: | matrix.os == 'ubuntu-20.04' && matrix.arch == 'x64' run: xvfb-run --auto-servernum -- npm test - name: Upload artifacts if: ${{ always() }} uses: actions/upload-artifact@v3 with: name: ${{ matrix.os }}-${{ matrix.arch }} path: | out/make test/output - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: "out/make/**" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. name: "CodeQL" on: push: branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] schedule: - cron: '0 5 * * 1' jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] language: ['javascript'] # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - name: Checkout repository uses: actions/checkout@v3 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 ================================================ FILE: .github/workflows/electronegativity.yml ================================================ name: "Electronegativity" on: push: jobs: build_job: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '12' - uses: doyensec/electronegativity-action@v1.1 - name: Upload sarif uses: github/codeql-action/upload-sarif@v2 with: sarif_file: ../results ================================================ FILE: .github/workflows/release-notes.yml ================================================ name: Release Notes on: release: types: - created jobs: create_notes: runs-on: ubuntu-latest steps: - name: Context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v3 with: fetch-depth: 1 - name: Setup Node uses: actions/setup-node@v3 with: node-version: 14.x - name: Run Gren env: GREN_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | npx github-release-notes release -o ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock .DS_Store # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # Webpack .webpack/ # Electron-Forge out/ ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" # empty for now ================================================ FILE: .husky/pre-push ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" # Check if there are any outstanding changes # NOTE: This is necessary as husky will run checks on pushing, # while there might be changes that are not yet committed interfering # with the tests and linter git diff HEAD --quiet npm run lint npm run make npm test ================================================ FILE: .npmrc ================================================ legacy-peer-deps=true ================================================ FILE: .vscode/debug-launcher.sh ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ARGS=$@ ARGS=${ARGS// /\~ \~} node "$DIR/../node_modules/@electron-forge/cli/dist/electron-forge-start" --vscode -- \~$ARGS\~ ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "pwa-node", "request": "launch", "name": "Electron Main", "program": "${workspaceFolder}/node_modules/@electron-forge/cli/dist/electron-forge-start.js", "args": [ "--vscode" ], "cwd": "${workspaceFolder}", "sourceMaps": true, "windows": { "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron-forge-vscode-win.cmd", "console": "internalConsole", "outputCapture": "std", }, "resolveSourceMapLocations": [ "${workspaceFolder}/**", "!**/node_modules/**" ] } ] } ================================================ FILE: .vscode/settings.json ================================================ { "eslint.enable": true } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at lei@codified.nl. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Aeon Aeon welcomes all contributions to it warmly and with open arms. ## GitHub Issues GitHub issues is our main method for tracking project progress, feature requests, bugs, etc. If you have an idea for a new implementation, just open a new GitHub issue, and we'll try to get you on the way to help with contributing. ## Documentation Documentation for inner workings of Aeon are available as part of the [Aeon documentation](https://docs.aeon.technology). Please refer to this document when you are trying to implement new features. In case you get stuck, or need help, please create a GitHub issue! ## Coding Conventions All code is written with four-spaced tabs. We have ESLint setup, and we kindly request you to lint your code before submitting it for a pull request: `npm run lint`. ================================================ FILE: LICENSE ================================================ European Union Public Licence V. 1.2 EUPL © the European Union 2007, 2016 This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such use is covered by a right of the copyright holder of the Work). The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following notice immediately following the copyright notice for the Work: “Licensed under the EUPL”, or has expressed by any other means his willingness to license under the EUPL. 1. Definitions In this Licence, the following terms have the following meaning: — ‘The Licence’: this Licence. — ‘The Original Work’: the work or software distributed or communicated by the ‘Licensor under this Licence, available as Source Code and also as ‘Executable Code as the case may be. — ‘Derivative Works’: the works or software that could be created by the ‘Licensee, based upon the Original Work or modifications thereof. This ‘Licence does not define the extent of modification or dependence on the ‘Original Work required in order to classify a work as a Derivative Work; ‘this extent is determined by copyright law applicable in the country ‘mentioned in Article 15. — ‘The Work’: the Original Work or its Derivative Works. — ‘The Source Code’: the human-readable form of the Work which is the most convenient for people to study and modify. — ‘The Executable Code’: any code which has generally been compiled and which is meant to be interpreted by a computer as a program. — ‘The Licensor’: the natural or legal person that distributes or communicates the Work under the Licence. — ‘Contributor(s)’: any natural or legal person who modifies the Work under the Licence, or otherwise contributes to the creation of a Derivative Work. — ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of the Work under the terms of the Licence. — ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, renting, distributing, communicating, transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential functionalities at the disposal of any other natural or legal person. 2. Scope of the rights granted by the Licence The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for the duration of copyright vested in the Original Work: — use the Work in any circumstance and for all usage, — reproduce the Work, — modify the Work, and make Derivative Works based upon the Work, — communicate to the public, including the right to make available or display the Work or copies thereof to the public and perform publicly, as the case may be, the Work, — distribute the Work or copies thereof, — lend and rent the Work or copies thereof, — sublicense rights in the Work or copies thereof. Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the applicable law permits so. In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed by law in order to make effective the licence of the economic rights here above listed. The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the extent necessary to make use of the rights granted on the Work under this Licence. 3. Communication of the Source Code The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to distribute or communicate the Work. 4. Limitations on copyright Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations thereto. 5. Obligations of the Licensee The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those obligations are the following: Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work to carry prominent notices stating that the Work has been modified and the date of modification. Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless the Original Work is expressly distributed only under this version of the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the Work or Derivative Work that alter or restrict the terms of the Licence. Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail. Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available for as long as the Licensee continues to distribute or communicate the Work. Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the copyright notice. 6. Chain of Authorship The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or licensed to him/her and that he/she has the power and authority to grant the Licence. Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or licensed to him/her and that he/she has the power and authority to grant the Licence. Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions to the Work, under the terms of this Licence. 7. Disclaimer of Warranty The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work and may therefore contain defects or ‘bugs’ inherent to this type of development. For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this Licence. This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work. 8. Disclaimer of Liability Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However, the Licensor will be liable under statutory product liability laws as far such laws apply to the Work. 9. Additional agreements While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by the fact You have accepted any warranty or additional liability. 10. Acceptance of the Licence The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms and conditions. Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution or Communication by You of the Work or copies thereof. 11. Information to the public In case of any Distribution or Communication of the Work by means of electronic communication by You (for example, by offering to download the Work from a remote location) the distribution channel or media (for example, a website) must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence and the way it may be accessible, concluded, stored and reproduced by the Licensee. 12. Termination of the Licence The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms of the Licence. Such a termination will not terminate the licences of any person who has received the Work from the Licensee under the Licence, provided such persons remain in full compliance with the Licence. 13. Miscellaneous Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the Work. If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid and enforceable. The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. New versions of the Licence will be published with a unique version number. All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take advantage of the linguistic version of their choice. 14. Jurisdiction Without prejudice to specific agreement between parties, — any litigation resulting from the interpretation of this License, arising between the European Union institutions, bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union, — any litigation arising between other parties and resulting from the interpretation of this License, will be subject to the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business. 15. Applicable Law Without prejudice to specific agreement between parties, — this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, resides or has his registered office, — this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside a European Union Member State. Appendix ‘Compatible Licences’ according to Article 5 EUPL are: — GNU General Public License (GPL) v. 2, v. 3 — GNU Affero General Public License (AGPL) v. 3 — Open Software License (OSL) v. 2.1, v. 3.0 — Eclipse Public License (EPL) v. 1.0 — CeCILL v. 2.0, v. 2.1 — Mozilla Public Licence (MPL) v. 2 — GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 — Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software — European Union Public Licence (EUPL) v. 1.1, v. 1.2 — Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+) — The European Commission may update this Appendix to later versions of the above licences without producing a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the covered Source Code from exclusive appropriation. — All other changes or additions to this Appendix require the production of a new EUPL version. ================================================ FILE: README.md ================================================ ![Aeon](./docs/.gitbook/assets/aeon-logo-whitespace.png)

📡 Scan the internet for your personal information and modify or remove it


License GitHub package.json version Documentation


# What is Aeon? 📡  Ever wondered what personal information is scattered around the internet? Aeon scans popuplar platforms for your personal information and (*almost*) automatically retrieves it. 👀  Use Aeon to download, archive and visualise your personal information. ❌  Don't agree with the data Facebook (or another platform) knows about you? Generate a request for modification or deletion with the click of a button!


# Installing Aeon Aeon is available for Windows, macOS, apt and yum! Download the application here and follow the instructions on [the download page](https://aeon.technology/download). [![Download Aeon](./docs/.gitbook/assets/download-button.svg)](https://aeon.technology/download) Not sure how to start using Aeon? Follow the [Getting Started guide](https://docs.aeon.technology/using-aeon/getting-started) for detailed instructions. # How does it work? All companies worldwide are required to offer you access to all data they retain about you. But most of the time, this process is hard, convoluted, slow or all three at once! Aeon has rules for each platform that make requesting your personal information just a couple of clicks!

The resulting data is downloaded on your local computer for safekeeping. Often, data is formatted in machine-readable formats such as JSON and CSV. To make it easier to digest your personal information, Aeon visualises it for you.

Companies are not only required to grant access, they must respect your wishes too. This means you can require them to delete or modify the personal information they have. Aeon contains a generator for data subject rights emails, that help you take control of your personal data.

# Supported Platforms Aeon currently has native support for the following platforms: * Facebook * Instagram * LinkedIn * Spotify * ...more coming soon Want to see a particular platform added? [Create a GitHub issue](https://github.com/leinelissen/aeon/issues/new/choose) with the name of the particular provider. Want to help out with adding new platforms? Providers are easily defined with a Provider config. [Check out the documentation on Providers](https://docs.aeon.technology/extending-aeon/architecture/providers) to find out how they work. You can always create a Pull Request # Contributing Aeon is being developed out in the open. Have an idea for a feature or a suggestion for a new provider? [Create a GitHub issue](https://github.com/leinelissen/aeon/issues/new/choose) and tag me (@leinelissen) if you need any help. # Documentation [![Read the documentation](./docs/.gitbook/assets/documentation-button.svg)](https://aeon.technology/download) ## Using Aeon You can find the latest build of Aeon over at the [releases page](https://github.com/leinelissen/aeon/releases). There's builds for Windows, macOS and Linux. If you're feeling more adventurous, clone the repository and compile your own nightly build. The only dependency is [NodeJS](https://nodejs.org/en/download/package-manager/). Prepare the codebase and start a development build by running the following commands: ``` npm install npm start ``` ## The Technical Stuff Aeon is an [Electron](https://www.electronjs.org/)-based app, a mature platform for building JavaScript applications on the desktop. It is backed by a locally encrypted Git repository, made available through use of the excellent [nodegit](https://www.nodegit.org/) package. A custom and modular back-end allows for tracking and retrieving data from multiple sources. This is done through retrieval from an API, asynchronous data requests or a combination of both. Parser logic then allows for extracting common data types from the resulting JSON or CSV. ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions Aeon is currently in open beta. Until a v1 is released, only the latest version (as per GitHub releases) is supported. No official support is offered for self-compiled Aeon versions, but any questions are allowed to be posted to the GitHub Issues. ## Reporting a Vulnerability In case you find a vulnerability in Aeon, please contact [lei@codified.nl](mailto:lei@codified.nl). You can find a PGP key at the bottom of this file. As soon as your report is received, we will respond to your report within 7 days, indicating necessary changes and required time for addressing the vulnerability. We kindly ask you to not share any details until a fix adressing the issue has been released.
GPG Key (lei@codified.nl) ``` -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBFgrN6gBEADDyFkpAlIkLFC19FD3K9At1liZUbrcxGdxpsihbd9di/15wTnY xMJgAQRZCZ68AXUqkmJaxEgNonfItSOcR8M+kBViNeOj+7hRUAy04QzjCleNDQ2Z Y6tfo7/b1uTbwkq7tWcMLP3MKQHuLLq8JQywWbq1hNKH3Ap7kJatO7UOyA0aL7lM 2Vo8fjRLdenQizJryrA6BqCglmSz/kKcbvOgES6fPMP2MllVxLM1wQDAhyv8VsKM X5Fjts2vBacGDxafIRVQzadE98fSKg0nlndY+ITRsV0dtNeN7rnqsuPIADLHpDDO tJ0H3haC4o/AuE01UtwhveLyu6RiMqEVvCGohLKPSCVbqvhQ+MDlH5xpHtI49fF6 dWLdAgMuTEynGiqU/TZR9LpYXdK6uRPQW4VEebRyo6oP7bDFh9uVlhRZnXc3ypxe Yy2Y1wxzAOv5dSFCH8jxoUWGIOL0YDPjCjoYR9GtujFkKQt5Y5BRiiXfY7vYVpFd NorLJbvj6+iXzQIA4uoPLC31SETlMy3toJnWSIjEe9dp/6TVXzyuApFBa00lm6pz zgz2nyDrtSE8+44LjHbe3m6w6Wtet2B2jKFOvfnqfS7QzQIyiJhufNOoXDkLrZV8 RoKQg0DUtfxCKR7LDYk6GSWmp55OOGF/0z3N3wBfFOc00XMVEXE/3t8HtQARAQAB tB5MZWkgTmVsaXNzZW4gPGxlaUBjb2RpZmllZC5ubD6JAjkEEwEIACMFAlgrN6gC GwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAAKCRAln6nAUV1QX/oNEAC9o/aX CbWzErt6UmHGFI1lWcvFeGDJnTP7ZppFnY6Ru0zxjYrNs1tXjROxsUZoTA1Z8H3Y FTSK27HT0d0MtCAMfwzWaUvfOTKMeoTZY8Tn6XLV6gHSlnCadTfmd7Os+V7qy2xA MF/OKTnskQsObJnYxMuungHMC6XFzE895OKnGl+uhrJyZ8Pw475bODVbIyIFLuJc +Tm/Ty9euL/EwDfw6CW3S7FBQH+gAP+CN0wF6efRfYICvRIaFsPwJkeWuWyfKfCL cptXakLi+RwK/f71nf/pX+uEe4xLPHAQWTeIWPKu4Z4kIkDU0Wesd5V9MCFZ9U4t SVbAL634gGz88x6UVKtFhG1GDhU4QO4hIFBtDFescAAZ8wmfeAGoYt7BKRGk7VwS ojNj5cpcQCMGLq2kMb6Io6A2RTw+W3BgdRJ3Qkz79k1AU/COF/48H2SCt6gNWbdm 37RpkUInWXo6UHd7kQmwttAlbA+UXykU9YZtVlzgSCBA8Y1qdPjqlmlSJiHVHKex oYVSZIpNmo5XSWmrMyj441UJGUaSP/ABh47H4DBSe1XVADhdNpLei0el0aCYscvp kkML4NdG9Jg7z2AFuMwAJEPM+Wqkio1RgaM+ppG2eKsqi1Je6kWBL+dGl6Ho9zew FPXDbjppPi4rn4G2KwWih/O2GkCuiTmeGlvEvbkCDQRYKzeoARAAwgE/0oUWKhHi LG0CDxahkxKLF7npLa+DAxtpvVgYTp34W9etN85KutqHYc6eJQmR4JGUe0oS6Pz+ vS5lHxbMfz+7UoLbYheepjGyQ/BlU+9Xu1EoM2LVRtSVBvKgvlNOaHQWWOfjvqvf PWIlejKpQjm+JtGn1/1T+ogC8u3RqDdp8m0l7pfqvTJ41Di3kXtQvwlqXbMQLrHG zFrmyq9q7psAWUm4oEMVsBgUQBjbDrMx+IDpxldrjjhLwBjWNAB6+KF34ALjF7jY /0qb80t3GkLVl1D+OMiyoR7TDyHYWLyQseLEqjgfEIFjJyTeWmvZPSHFjfwZ0lKv pPbFjh3bJNaEiFdOV1gyYLjuJCGTw2wes01qQbvh3Ovsz8gpPsRaxD2uy5+6/EvK dnUNKd5W66pwiAYCIf7FoLUnwSyie12CM5TvbDXDaGgnI8UQQ4UyQXCcCDL5YigM ozvJAYuKJ6V3Aou5T+ub4y+jJOZSICWzkwtgYtzj3Jn0C01KvoK7zunkefMXzSSL nEAJiw/pbpDy0cKZRsq8wol4S14dwuQSXMFt0plIb2Hs4oWMmmqPg5rfKzybMpmU ggkjiCVI5sd5KpRtkm/QVcUsS+j2DTQ4alrHJHpmILGkxTJ4ahE/+06LZyEO8wYN 2AMEShKhy4PTiLihY4J7eoL4LFefPNEAEQEAAYkCHwQYAQgACQUCWCs3qAIbDAAK CRAln6nAUV1QX2rUD/4nPos5hJadjrbsj3vzx9/IBby7oXQe7sq3za0vCZpe4pmF NRE1GEujnDp7NdRkS7i0CLA4PI/CHAYr5ltkmJoeYI9RP+Ix2nvlykRgbPrpFbdh 2P9FY+zF3p/7elW91/zOKQ2GQcOOYewEu5Q7zuLCfuDz7crf7pImzxs7BO974s3p 4qL+PkOOK+YczGjvMT6Ewglz0oEdMUuvUNYhIdRsoTvSmYsMbhSMt1aKTGrkkG0g g3pRWrswdKMzoBvheSgFt/jdm2prQLjjmjUXqOsyiZPI787MgEv60n2rkScl1IwM 2ia04hU644jrGwpddyD0CtisRDa/aDljRNU/0K1VYGjN2rAcdMP2vg52kpDzpAMs 9t6HbqIFcTFKFhFrQtRy8Rt+gm6fLm164Pm0dPpTzZhrkyTWQOjp4X5TvL6uOh4L dnID0SlFu4iw0HHDqqynkRgABSHyo5V333bsSuuxhzhwt69tb5ipGxr+ZQ3c1e7R qtAERkY4Zzer2im/CW+2qg3wyB/FQWsaEy9gtfqUCUiOMcaYNQEhPtWu3YgEcJYP hdngamPzSpkMf5KbPbQxz8Sz2OyZJMf4vTH7VpG4RhDceY22grzGknl/NxZ4Cdqo k+6qAbrYL6msx9TAH2Q0HuWujyvilepHIvR8yAVjUHcoqkjyNk5bBJVAmC/Uwg== =MlxM -----END PGP PUBLIC KEY BLOCK----- ```
================================================ FILE: data/.gitignore ================================================ * !.gitignore ================================================ FILE: docs/README.md ================================================ # Aeon Documentation ![](<.gitbook/assets/aeon-whitespace-2x (1).png>) Welcome to the Aeon documentation, which attempts to make it as easy as possible for you to use and extend the Aeon tool. ![](<.gitbook/assets/schermafbeelding-2020-12-15-om-15.48.04 (1).png>) ### What does Aeon do? Aeon is a tool that is designed to make getting your personal data that is stored by organisations as easy as possible. You can add all the tools and organisations you work with, and Aeon will reach out to them to get your data back. Once you do get the data, Aeon shows you what has changed in it, and give you the tools to start adding, modifying or updating said data. All made as easy as possible. ![](<.gitbook/assets/schermafbeelding-2020-12-15-om-16.41.06 (1).png>) ### How do I use the documentation? This documentation is split into two sections. First, we discuss how to use Aeon as an end-user. Secondly, we discuss how Aeon can be extended as a developer, to tailor it to the use cases you desire. {% content-ref url="using-aeon/installation.md" %} [installation.md](using-aeon/installation.md) {% endcontent-ref %} {% content-ref url="extending-aeon/local-development.md" %} [local-development.md](extending-aeon/local-development.md) {% endcontent-ref %} ================================================ FILE: docs/SUMMARY.md ================================================ # Table of contents * [Aeon Documentation](README.md) ## Using Aeon * [Installation](using-aeon/installation.md) * [Getting Started](using-aeon/getting-started.md) ## Extending Aeon * [Local Development](extending-aeon/local-development.md) * [Reporting Issues](extending-aeon/reporting-issues.md) * [Architecture](extending-aeon/architecture/README.md) * [Providers](extending-aeon/architecture/providers.md) ================================================ FILE: docs/extending-aeon/architecture/README.md ================================================ # Architecture Aeon is built to reach out to various platforms, gather data, store it and then report it to the user. This page will go into detail into how these goals are worked out technically. ## Technologies ### TypeScript The entire Aeon codebase is written in [TypeScript](https://www.typescriptlang.org/). TypeScript is a language that compiles to Javascript, with the additional benefit of types, describing inputs outputs and datatypes. TypeScript is not only used for a better developer experience, but the data typing system actually relies heavily on TypeScript enums and data formats. ### Electron Aeon is built on Electron, and leverages its integration with browsers heavily to use existing data download workflows to gather data. Electronc consists of a NodeJS back-end \(called `main` in Electron parlance and the Aeon codebase\) coupled with a Chromium front-end \(dito, called `app`\). You'll find that the codebase is neatly seperated along these lines, with the `src/main` folder containg all back-end code and the `src/app` folder containing all front-end code. Following Electron best-practices, these environments run seperately and are sandboxes, to prevent external webpages from getting system-level access through NodeJS. This means that communication between the front-end and back-end happens through a set of [bridges](https://www.electronjs.org/docs/api/context-bridge) \(for example, see the [RepositoryBridge](https://github.com/leinelissen/aeon/blob/master/src/main/lib/repository/bridge.ts) and the app-side [Repository utility](https://github.com/leinelissen/aeon/blob/master/src/app/utilities/Repository.ts)\). All front-end calls to the back-end should go through these bridges to ensure security. Additionally, [the preload file](https://github.com/leinelissen/aeon/blob/master/src/app/preload.ts) specifies some unique cases where the front-end has access to back-end methods. ### React The front-end of the application is built out using [React](https://reactjs.org/), a Javascript framework that allows the building out of reactive interfaces in browsers. The `src/app` folder contains the entire front-end and is seperated out into assets, components, pages, store, style and utilities. Each interface screen is specified in `src/app/pages` and may pull in components from other directories. Styling is done using [Styled Components](https://styled-components.com/), routing using [React Router](https://reactrouter.com/) and the store is backed by [undux](https://undux.org/). ### Git In order to version incoming data, Aeon builds and updates a local Git repository that is bundled with the application. Aeon interfaces with this repository through [NodeGit](https://www.nodegit.org/), a set of NodeJS bindings for the widely used libgit2 library. All interfacing with the repository is done in NodeJS, though there is a bridge available for calling certain functions from the front-end. ## Concepts ### Providers The first point of Aeon is making sure data is retrieved from a series of sources. These sources are abstracted into **Providers**. In Aeon, a _Provider_ is a binding for a particular platform \(e.g. Facebook, Instagram, LinkedIn\) that can directly pull data, initiate data requests, download data and parse data. For more detailed info on the inner workings of providers, and how to write one, find the particular page. > {% page-ref page="providers.md" %} ================================================ FILE: docs/extending-aeon/architecture/providers.md ================================================ # Providers The first point of Aeon is making sure data is retrieved from a series of sources. These sources are abstracted into **Providers**. In Aeon, a _Provider_ is a binding for a particular platform \(e.g. Facebook, Instagram, LinkedIn\) that can directly pull data, initiate data requests, download data and parse data. ### Anatomy Each Provider owns their own folder in `src/main/providers` , and may consist of several files making up two distinct elements: the Provider itself \(a class that either implements `Provider` or `DataRequestProvider`\), and a Parser that interprets the resulting data for visualisation. For instance, see the [Instagram Provider](https://github.com/leinelissen/aeon/blob/master/src/main/providers/instagram/index.ts) and [Instagram Parser](https://github.com/leinelissen/aeon/blob/master/src/main/providers/instagram/parser.ts). The anatomy of what a Provider, Parser and datatypes look like are described in Typescript, and are available in the [Provider types file](https://github.com/leinelissen/aeon/blob/master/src/main/providers/types.ts). This should be a regular reference when starting to build out your own providers. ### Provider A Provider is a class that implements the bindings for a particular service. In this example we'll focus on Instagram. Each provider follows a particular workflow that covers a set of methods a platform must support: `initialise`, `update`, `dispatchDataRequest`, `isDataRequestComplete` and `parseDataRequest`. Please note that a Provider distinguishes between immediately accessible data \(i.e. that can be directly downloaded via an API\) as _updates,_ while data that takes a while to gather and is downloaded in one go is considered as _data requests_. #### Initialisation When the user wants to add an account for a certain platform, a new Provider is instantiated, and the thew `initialise` function is called. The point of this function is to authenticate a user to said platform, so that we can make further calls to the platform. In the case of Instagram, a very basic implementation of this looks as follows: ```typescript class Instagram extends Provider { initialise(): Promise { return withSecureWindow(windowParams, (window) => { // Load a URL in the browser, and see if we get redirected or not const profileUrl = 'https://www.instagram.com/accounts/access_tool/ads_interests'; window.loadURL(profileUrl); // Create a promise that should resolve when the user is logged in return new Promise((resolve) => { window.webContents.on('did-navigate', () => { if (profileUrl === window.webContents.getURL()) { resolve(); } }); }); }); } } ``` A BrowserWindow is opened containing a link to a protected URL. Instagram will normally redirect us to the login page, which is then shown to the user. We then wait for the login form to succeed and redirect us back to the original, protected URL. When we detect this change, the user has successfully logged in and we can return true. We can later use the cookies that are set in this instance, to send out specific requests. _Small note: you'll find that this implementation slightly differs from the_ [_actual implementation_](https://github.com/leinelissen/aeon/blob/master/src/main/providers/instagram/index.ts)_, mostly in retrieving and setting cookies relevant for later use. This is the simplest implementation, but you should find inspiration in the fully-fledged implementations that are available in the repository._ #### Updates Now that we have a set of cookies, we can talk to any Instagram data as if we are the user. Fortunately, Instagram has a set of data APIs available that we can immediately gather our data from:import scrapingUrls from './urls.json'; ```typescript import scrapingUrls from './urls.json'; // eg: [ // "https://www.instagram.com/accounts/access_tool/account_privacy_changes?__a=1", // "https://www.instagram.com/accounts/access_tool/password_changes?__a=1", // "https://www.instagram.com/accounts/access_tool/former_emails?__a=1", // .... class Instagram extends Provider { update = async (): Promise => { const cookies = await this.verifyLoggedInStatus(); // We extract the right cookies, and create a config we can then // use for successive requests const sessionid = cookies.find(cookie => cookie.name === 'sessionid').value; // Then, we'll setup a config for each individual fetch requests const fetchConfig = { method: 'GET', headers: { Accept: 'application/json', Referer: 'https://www.instagram.com/accounts/access_tool/ads_interests', 'X-CSRFToken': crypto.randomBytes(20).toString('hex'), cookie: `sessionid=${sessionid}; shbid=${''}` }, }; // Now we do all API requests in order to retrieve the data const responses = await Promise.all( scrapingUrls.map(url => fetch(url, fetchConfig).then(response => response.json()) ) ); // We then transform the data so that we can return it to the handler return responses.map(response => { return { filepath: `${response.page_name}.json`, data: JSON.stringify(response.data.data, null, 4), }; }); } } ``` As you can see, we have a set of URLs available, and by stealing some of the cookies from the browser, we can just call all APIs, and return the data gathered from it to the ProviderManager. For this we follow the format `{ filepath: 'path_to_file', data: { //data }` . _Note that retrieving data from APIs may not as easy with all services. If you do not want to implement immediate updates, you can return `null` from this function._ #### Dispatch Data Requests Lots of services have an automated way of downloading your data. You click a button, wait some time, and then download a .ZIP file. Our goal is to automate the entirety of this process, starting with actually requesting the data. In our reference, Instagram has a dedicated page for these kinds of requests: ```typescript class Instagram extends Provider { async dispatchDataRequest(): Promise { return withSecureWindow(windowParams, async (window) => { // Load the dispatched window window.hide(); await new Promise((resolve) => { window.webContents.on('did-finish-load', resolve) window.loadURL('https://www.instagram.com/download/request/'); }); // We'll click the button for the user, but we'll need to defer to the // user for a password window.webContents.executeJavaScript(` Array.from(document.querySelectorAll('button')) .find(el => el.textContent === 'Next') .click?.() `); window.show(); // Now we must defer the page to the user, so that they can enter their // password. We then listen for a succesfull AJAX call return new Promise((resolve) => { window.webContents.session.webRequest.onCompleted({ urls: [ 'https://*.facebook.com/*' ] }, (details: Electron.OnCompletedListenerDetails) => { console.log('NEW REQUEST', details); if (details.url === 'https://www.facebook.com/api/graphql/' && details.statusCode === 200) { resolve(); } }); }); }); } } ``` As you can see, Instagram requires their user so enter a password before they can send out a request. To resolve this, we present this page to the user and wait for them to enter their password. We then use particular Electron listeners to wait for the form to be submitted. #### Check if Data Request is complete It then might take a while for the data request to resolve. We currently use a polling approach to periodically check the particular page to see if the request has been completed: ```typescript class Instagram extends Provider { async isDataRequestComplete(): Promise { return withSecureWindow(windowParams, async (window) => { // Load page URL await new Promise((resolve) => { window.webContents.once('did-finish-load', resolve) window.loadURL('https://www.instagram.com/download/request/'); }); // Find a heading that reads 'Your Download is Ready' return window.webContents.executeJavaScript(` !!Array.from(document.querySelectorAll('h1')) .find(el => el.textContent === 'Your Download is Ready'); `); }); } } ``` This comes down to loading the page and seeing if a particular element \(in this case a h1 tag reading "Your download is ready"\) is present on the page. This functions thus return true or false depending on whether the request is complete. #### Download the Data Request If the data request is complete, all that is left is downloading it and passing it back to Aeon. This is done as follows: ```typescript import { app } from 'electron'; import AdmZip from 'adm-zip'; import fs from 'fs'; const requestSavePath = path.join(app.getAppPath(), 'data'); class Instagram extends Provider { async parseDataRequest(extractionPath: string): Promise { return withSecureWindow(windowParams, async (window) => { // Load page URL await new Promise((resolve) => { window.webContents.once('did-finish-load', resolve) window.loadURL('https://www.instagram.com/download/request/'); }); await new Promise((resolve) => { // Now we defer to the user to enter their credentials window.webContents.once('did-navigate', resolve); window.webContents.executeJavaScript(` Array.from(document.querySelectorAll('button')) .find(el => el.textContent === 'Log In Again') .click?.() `); }); // We can now show the window for the login screen window.show(); // Then we'll await the navigation back to the data download page from // the login page await new Promise((resolve) => { window.webContents.once('will-navigate', resolve); }); // We can now close the window window.hide(); // Now that we're successfully authenticated on the data download page, // the only thing we have to do is download the data. const filePath = path.join(requestSavePath, 'instagram.zip'); await new Promise((resolve) => { // Create a handler for any file saving actions window.webContents.session.once('will-download', (event, item) => { // Save the item to the data folder temporarily item.setSavePath(filePath); item.once('done', resolve); }); // And then trigger the button click window.webContents.executeJavaScript(` Array.from(document.querySelectorAll('button')) .find(el => el.textContent === 'Download Data') .click?.() `); }); // We have the ZIP, all that's left to do is unpack it and pipe it to // the repository const zip = new AdmZip(filePath); await new Promise((resolve) => zip.extractAllToAsync(extractionPath, true, resolve) ); // Translate this into a form that is readable for the ParserManager const files = zip.getEntries().map((entry): ProviderFile => { return { filepath: entry.entryName, data: null, }; }); // And dont forget to remove the zip file after it's been processed await fs.promises.unlink(filePath); return files; }); } } ``` While this piece of code is a bit longer, the process is still quite simple. First, we click the download button for the user. As Instagram requires users to enter their password before downloading the file, we open the window to the user and wait for them to enter their password. We then wait for the download to be registered by the BrowserWindow, save it to a particular location, unpack it and return a list of the files that were created by this dump. Aeon will recognise this data, and create a new commit that includes all changed files from the dump. #### withSecureWindow In the reference implementations, you'll find ample use of the `withSecureWindow` function. This functions creates and spawns an [Electron BrowserWindow](https://www.electronjs.org/docs/api/browser-window) for the duration of a set of requests. It returns a promise that can be returned from the callback, and rejects if an error occurs, or the user closes the window. It makes it easy to create windows a user should interact with \(e.g. to log in or to manually click a button\), while keeping everything secure. If you wish to keep state \(i.e. cookies\) between different windows, you should specify a global `windowParams` object that is implemented in each call to `withSecureWindow`. ### Parsers Now that data is actually present in the repository, we need to parse into a readable format for the front-end. We do this by means of parsers. The basic layout for a parser is as follows: ```typescript const parsers: ProviderParser[] = [ { source: 'accounts_following_you.json', schemas: [ { key: 'text', type: ProvidedDataTypes.FOLLOWER } ], }, ]; ``` A parser is an array of files that have specific schemas attached to them. First, a new object is specified with a source: this is the filename for a particular file that is found in the repository folder for the particular Provider. Next, a schema is a mapping of a key found anywhere in that file, to a particular datatype. This means that in the file `accounts_following_you.json` , the schema looks for any value that is associated with the key `text`. This pieces of data is then associated with the `FOLLOWER` datatype when Aeon attempts to parse the data. In case there isn't a neat mapping, or some data needs to be converted, a `transformer` can be set on the schema that interprets and transforms the data: ```typescript import { parseISO } from 'date-fns'; /** * This specifies an input object in which the data is structured in an object, * in which the keys represent usernames, and the values are ISO dates. These * can be iterated upon to extract the desired data */ type GenericKeyedData = { [key: string]: string }; const parsers: ProviderParser[] = [ { source: 'connections.json', schemas: [ { key: 'following', type: ProvidedDataTypes.ACCOUNT_FOLLOWING, // Transform the data from { username: '9 Jan 2011', ... } to an array of // objects that store both the username and the date transformer: (obj: GenericKeyedData): Partial[] => { // Loop through each available key on the object return Object.keys(obj).map((key): Partial => { // Return an object in which the data is the key (username) // and the timestamp is a JS Date object. return { data: key, timestamp: parseISO(obj[key]), }; }); } } ], }, ]; ``` There are many more possibilities with the returned data and the transformer. Consult the [`ProviderParser` types](https://github.com/leinelissen/aeon/blob/master/src/main/providers/types.ts) to see what possibilities you can use. For instance, you can also specify multiple keys for the same schema. ### Registering Providers and Parsers If you have managed to setup your own providers and parsers, the last thing that is left is registering them with Aeon. This is done in three places: First, add the `Provider` class you created to the `providers` array in [`src/main/providers/index.ts`](https://github.com/leinelissen/aeon/blob/master/src/main/providers/index.ts). Then, add the resulting `ProviderParser` object to the `providerParsers` object in [`src/main/providers/parsers.ts`](https://github.com/leinelissen/aeon/blob/master/src/main/providers/parsers.ts). Lastly, add an icon for your provider to the `getIcon` method in [`src/app/utilities/Providers.ts`](https://github.com/leinelissen/aeon/blob/master/src/app/utilities/Providers.ts) to make sure your Provider appears pretty in the interface. Job well done! ================================================ FILE: docs/extending-aeon/local-development.md ================================================ # Local Development As Aeon is a desktop app, yout development happens locally as well. In this guide, we'll go step by step what you need to do, to get Aeon set up locally and get started on its development. ### Working with the terminal Aeon is built using command line tools \(sometimes abbreviated as CLI\). These are tools that don't use neat graphical interfaces, but a terminal interface: the old-school style text-based screen you usually only see in movies. To access the command line, you need to open up a terminal in your operating system. Either use one of your OS-provided terminals \(Terminal for macOS and Ubuntu; Command Prompt for Windows\), or find a cross-platform terminal app such as [Hyper](https://hyper.is/). We actually recommend working with [Visual Studio Code](https://code.visualstudio.com/) for this reason: it comes with a terminal built-in! You can open it up with Ctrl + Backtick on Windows/Linux and Cmd + Backtick on macOS. Do make sure you know how to operate your terminal before you start working on Aeon. To help you get along, find this [guide on how to operate the Ubuntu terminal](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview). You'll find that the macOS and Linux terminal are actually pretty similar once you get inside. These guides will assume you are working on a UNIX terminal \(e.g. bash, zsh, fish\), as these work across macOS, FreeBSD and Linux distributions. If you are on a Windows machine and you cannot translate these to Command Prompt or Powershell commands yourself, consider installing the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). This allows you to run a bash terminal on Windows, on which all commands in this guide should work flawlessly. ### Prerequisites Aeon is based on [Electron](https://www.electronjs.org/), a tool that combines the [Chromium](https://www.chromium.org/) browser \(on which [Chrome](https://www.google.com/chrome/) is built\) with a NodeJS \(best described as server-side Chromium again\) back-end in a single, executable desktop package. It is no suprise that everything is written in Javascript. In order to get started, you're going to need a couple of tools so that we can actually compile and run the application. These are as follows: #### Git A version control tool for code: Git helps you make small changes to a large codebase such as Aeon's. Either [install the binary directly](https://git-scm.com/downloads), or use one of your favorite package managers: Windows \([Chocolatey](https://chocolatey.org/docs/installation)\) ```text choco install git ``` macOS \([Homebrew](https://brew.sh/)\) ```text brew install git ``` Ubuntu \(apt\) ```text sudo apt-get install git ``` #### NodeJS A server runtime for JavaScript: helps you build powerful Javascript-based applications that can run on the desktop rather than the web! Either [install the binary directly](https://nodejs.org/en/) or use one of your favorite package managers: Windows \([Chocolatey](https://chocolatey.org/docs/installation)\) ```text choco install nodejs ``` macOS \([Homebrew](https://brew.sh/)\) ```text brew install node ``` For Linux installs, find the relevant package manager of your choice in the [NodeJS install guide](https://nodejs.org/en/download/package-manager/). ### Cloning the repository Now that you have installed all prerequisites, you need the source code that compiles to the Aeon application. Fortunately, this is accessible from GitHub and you can copy it to your computer very easily. First, make sure you navigate to the folder that will hold the folder containing all the Aeon code. Make sure you navigate to a particular folder on your own system. ```text cd Documents/Code ``` Now that we're in the directory, we're going to copy the entire codebase to a folder within this directory. This operation is called cloning in Git terminology. You do it as follows: ```text git clone https://github.com/leinelissen/aeon ``` When the command finishes, the Aeon codebase should be found in a folder called `aeon`. Navigate into this folder to get started on installing dependencies. ```text cd aeon ``` ### Installing dependencies Aeon builds upon lots of tools and libraries that make working with data, interfaces and other stuff a lot easier. These dependencies must be installed first, before we can start compiling the application. You do this as follows: ```text npm install ``` ### Development Mode In order to make development really easy, Aeon has a development mode that incrementally compiles Aeon and runs it immediately. This means that when you change a file, the application is re-compiled and reloaded. To start development mode, run the following command: ```text npm run start ``` #### Using the Visual Studio Code Debugger The repository also includes a basic setup for integrating the VSCode debugger. This allows you to inspect objects that are logged, set breakpoints and do object inspection while you are developing. To use it, either press `F5` or run `Electron Main` from the debugger tab. ### Compiling If you want to generate an application package for distribution, you can run a build command as follows: ```text npm run make ``` When the command finishes \(note: this may take a while\), you can find a build for your specific platform in the `out/make` folder. ### What's next? Now that you know how that make local development work, have a look at the architecture diagram, and how specific core concepts work. If you feel adventurous, you can get to work and start creating pull requests with new features. {% page-ref page="architecture/" %} {% page-ref page="reporting-issues.md" %} ================================================ FILE: docs/extending-aeon/reporting-issues.md ================================================ # Reporting Issues Aeon is still a work in progress, so you might run into issues while using, building or abusing the application. This is expected. Alternatively, you might have suggestions for new platforms to add, or other features you think are crucial to its development. We accept and welcome any issues or feedback you have for this application. In order to give some shape to this feedback, please use the GitHub issue tracker to report anything you find interesting. You can [create a new issue here](https://github.com/leinelissen/aeon/issues/new/choose) or [browse through existing issues here](https://github.com/leinelissen/aeon/issues). If you find an existing issue already matches your feedback, please consider adding your feedback to the already existing issue. ================================================ FILE: docs/using-aeon/getting-started.md ================================================ # Getting Started Not sure how to get started with Aeon? Let us guide you through a few crucial steps to get started with requesting and owning your personal information! Make sure you have installed Aeon first. If you don't know how to do so, [follow our guide](installation.md). ### Adding Accounts First, find the accounts tab in the left menu. ![](<../.gitbook/assets/Schermafbeelding 2021-11-12 om 14.49.20.png>) Then, click the **Add New Account** button on the bottom of the screen. ![](<../.gitbook/assets/Schermafbeelding 2021-11-12 om 14.49.58.png>) A screen pops up, in which you can select a particular provided you would like to add. Click the platform of your choice (in this case Facebook) and the click the **Add New Facebook Account** button. Once you have clicked the button, a new window will pop up for logging in to the platform. Enter your credentials and click login. Note: Aeon never stores your password, and all tokens that are kept on your behalf are stored locally and safely. Your account is now set up, and you can feel free to add other accounts too! ### Starting Data Requests If your accounts are all set up, you can browser through them in the Accounts tab. ![](../.gitbook/assets/accounts.webp) If you wish to retrieve data, select an account and click **Start Data Request**. You might need to enter your credentials again. Data requests often take time. If your request is started successfully, you can kick it back and wait the hours / days it takes for the data request to complete. Aeon will let you know when the request is done. ### Completing Data Requests Whenever a data request is finished, Aeon will let you know. You can complete a finished data request by going to the account and clicking **Complete Data Request**. Your data will be downloaded into Aeon. ### Visualising Data Once your data is in Aeon, you can use its powerful visualisation tools to see what is going on with your data. Find the Graph tab on the left side, for instance. ![](<../.gitbook/assets/Schermafbeelding 2021-11-12 om 14.50.48.png>) The large blue balls represent platforms, the squares represent data types, and the small dots represent a single data point that was retrieved from the platform. If you like, you can click an individual data point to inspect it: ![](<../.gitbook/assets/Schermafbeelding 2021-11-12 om 14.51.31.png>) ### Removing Data Feel like a platform should not be having one of your data points? You can automatically generate a request for deleting it. Click the **Delete This Data Point** button in any data visualisation to add it to data points slated for deleting. ![](<../.gitbook/assets/Schermafbeelding 2021-11-12 om 14.51.31 (1).png>) If you are sure you want to delete the data points, go to the **Erasure (1)** tab on the left-hand side. ![](<../.gitbook/assets/Schermafbeelding 2021-11-12 om 14.51.40.png>) Aeon will then display a list of datapoints that you have selected. Click **Remove 1 Data Point **to generate the request. ![](<../.gitbook/assets/Schermafbeelding 2021-11-12 om 15.05.08.png>) An email has been generated for each platform of which data is deleted. You will need to send this email yourself. Click **Send in E-mail Client** to open your default email client, so you can send the email. ================================================ FILE: docs/using-aeon/installation.md ================================================ # Installation Installing Aeon is really easy: [find the latest release on GitHub](https://github.com/leinelissen/aeon/releases/latest), and download the right package for your architecture. At this moment the following operating systems are supported. * Windows (with setup.exe or with NuGet: .nupkg) * macOS (the darwin .zip) * Ubuntu / Debian (the .deb file) * Fedora / CentOS / RHEL (the .rpm file) If you find a platform you are regularly using missing, please create an issue, or find our guide to compiling your own version. ARM is theoretically supported, but please do report any issues you encounter while trying to do so. ### Getting Started Not sure how to get started? Follow our guide: {% content-ref url="getting-started.md" %} [getting-started.md](getting-started.md) {% endcontent-ref %} Run into issues? Make sure you report them, so that we can improve on the software: {% content-ref url="../extending-aeon/reporting-issues.md" %} [reporting-issues.md](../extending-aeon/reporting-issues.md) {% endcontent-ref %} ================================================ FILE: entitlements.plist ================================================ com.apple.security.cs.allow-jit com.apple.security.cs.debugger ================================================ FILE: forge.config.js ================================================ const package = require('./package.json'); const hash = require('child_process') .execSync('git rev-parse --short HEAD') .toString().trim() /** * This is the base electron-forge config */ const config = { packagerConfig: { name: process.platform === 'linux' ? 'aeon' : 'Aeon', icon: './src/icon', executableName: process.platform === 'linux' ? 'aeon' : 'Aeon', asar: false, buildVersion: `${package.version}-${hash}`, protocols: [ { name: "Aeon", schemes: ["aeon"] } ] }, makers: [ { name: '@electron-forge/maker-squirrel', config: { iconUrl: 'https://raw.githubusercontent.com/leinelissen/aeon/master/src/icon.ico', setupIcon: './src/icon.ico' } }, { name: '@electron-forge/maker-dmg', config: { // background: './assets/dmg-background.png', format: 'ULFO' } }, { name: "@electron-forge/maker-zip", platforms: [ "darwin" ], }, { name: '@electron-forge/maker-deb', config: {} }, { name: '@electron-forge/maker-rpm', config: {} } ], plugins: [ { name: '@electron-forge/plugin-webpack', config: { // HMR Woes: https://github.com/electron-userland/electron-forge/issues/2560 devServer: { liveReload: false, }, mainConfig: './webpack.main.config.js', renderer: { config: './webpack.renderer.config.js', entryPoints: [ { html: './src/app/index.ejs', js: './src/app/index.tsx', name: 'main_window', preload: { js: './src/app/preload.ts', }, } ] }, loggerPort: 9001 } }, ], }; /** * This function inserts config for notarizing applications. * Idea stolen from: https://github.com/electron/fiddle/blob/master/forge.config.js */ function notarizeMaybe() { // GUARD: Only notarize macOS-based applications if (process.platform !== 'darwin') { return; } // Only notarize in CI if (!process.env.CI) { console.log(`Not in CI, skipping notarization`); return; } // GUARD: Credentials are required if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) { console.warn( 'Should be notarizing, but environment variables APPLE_ID or APPLE_ID_PASSWORD are missing!', ); return; } // Inject the notarization config if everything is right config.packagerConfig.osxNotarize = { appBundleId: 'nl.leinelissen.aeon', appleId: process.env.APPLE_ID, appleIdPassword: process.env.APPLE_ID_PASSWORD, ascProvider: '238P3C58WC', }; // Also inject signing config config.packagerConfig.osxSign = { identity: 'Developer ID Application: Bureau Moeilijke Dingen BV (238P3C58WC)', 'hardened-runtime': true, entitlements: 'entitlements.plist', 'entitlements-inherit': 'entitlements.plist', 'signature-flags': 'library', }; } notarizeMaybe(); /** * For some reason OpenSSL isn't compiled directly into the NodeGit native module. We * thus have to include manually on Windows only. */ function bundleOpenSSLMaybe() { if (process.platform !== 'win32') { return; } // Add a hook to include the file config.hooks = { postPackage: async () => { const fs = require('fs'); const path = require('path'); await fs.promises.copyFile( // TODO: It's probably a bad idea to hardcode the DLL location here. Maybe it // is a good idea to pull it from some side of config or environment variable. 'C:\\WINDOWS\\system32\\libcrypto-1_1-x64.dll', path.join(__dirname, 'out', 'Aeon-win32-x64', 'resources', 'app', '.webpack', 'main', 'native_modules', 'build', 'Release', 'libcrypto-1_1-x64.dll'), ); } }; } bundleOpenSSLMaybe(); // Finally, export it module.exports = config; ================================================ FILE: package.json ================================================ { "name": "aeon", "productName": "Aeon", "version": "0.2.22", "description": "Online identity versioning for the masses", "main": ".webpack/main", "scripts": { "start": "electron-forge start", "package": "electron-forge package", "make": "cross-env NODE_ENV=production electron-forge make", "publish": "electron-forge publish", "lint": "eslint --ext .ts,.tsx ./src && tsc --noEmit", "test": "playwright test", "prepare:nodegit": "node scripts/prepareNodegit.js", "rebuild:native-modules": "electron-rebuild", "postinstall": "npm run prepare:nodegit", "prepare": "husky install" }, "keywords": [], "author": { "name": "Lei Nelissen", "email": "lei@codified.nl" }, "license": "EUPL-1.2", "repository": { "type": "git", "url": "https://github.com/leinelissen/aeon" }, "devDependencies": { "@electron-forge/cli": "6.1.0", "@electron-forge/maker-deb": "6.1.0", "@electron-forge/maker-dmg": "6.1.0", "@electron-forge/maker-rpm": "6.1.0", "@electron-forge/maker-squirrel": "6.1.0", "@electron-forge/maker-zip": "6.1.0", "@electron-forge/plugin-webpack": "6.1.0", "@playwright/test": "1.31", "@swc/core": "1.3.42", "@types/adm-zip": "0.5.0", "@types/cytoscape": "3.19.9", "@types/imapflow": "^1.0.9", "@types/lodash-es": "4.17.7", "@types/mailparser": "3.4.0", "@types/node": "16.18.22", "@types/node-fetch": "3.0.3", "@types/nodegit": "0.28.3", "@types/nodemailer": "6.4.7", "@types/react": "17.0.55", "@types/react-dom": "18.0.11", "@types/react-redux": "7.1.25", "@types/react-select": "5.0.1", "@types/source-map-support": "0.5.6", "@types/stream-chain": "2.0.1", "@types/styled-components": "5.1.26", "@types/yargs": "17.0.24", "@typescript-eslint/eslint-plugin": "5.57.0", "@typescript-eslint/parser": "5.57.0", "@vercel/webpack-asset-relocator-loader": "1.7.3", "cross-env": "7.0.3", "csp-html-webpack-plugin": "5.1.0", "css-loader": "6.7.3", "dotenv-webpack": "7.1.1", "electron": "23.2.0", "electron-devtools-installer": "3.2.0", "eslint": "8.37.0", "eslint-config-airbnb": "19.0.4", "eslint-config-airbnb-typescript": "17.0.0", "eslint-import-resolver-typescript": "3.5.4", "eslint-plugin-deprecation": "1.3.3", "eslint-plugin-import": "2.27.5", "eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", "husky": "8.0.3", "mini-css-extract-plugin": "2.7.5", "node-loader": "2.0.0", "playwright": "1.31", "swc-loader": "0.2.3", "typescript": "4.9.5", "webpack-bundle-analyzer": "4.8.0" }, "dependencies": { "@fast-csv/parse": "4.3.6", "@fontsource/ibm-plex-mono": "4.5.13", "@fontsource/ibm-plex-sans": "4.5.13", "@fontsource/inter": "4.5.15", "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/free-brands-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/react-fontawesome": "0.2.0", "@metrichor/jmespath": "0.3.1", "@pmmmwh/react-refresh-webpack-plugin": "0.5.10", "@popperjs/core": "2.11.7", "@reactour/tour": "2.13.0", "@reduxjs/toolkit": "1.9.3", "adm-zip": "0.5.10", "cytoscape": "3.23.0", "cytoscape-fcose": "2.2.0", "date-fns": "2.29.3", "electron-squirrel-startup": "1.0.0", "electron-store": "8.1.0", "eventemitter2": "6.4.9", "history": "5.3.0", "imapflow": "1.0.125", "keytar": "7.9.0", "lodash-es": "4.17.21", "mailparser": "3.6.4", "node-abi": "3.33.0", "node-fetch": "3.3.1", "nodegit": "0.28.0-alpha.21", "nodemailer": "6.9.1", "react": "18.2.0", "react-dom": "18.2.0", "react-popper": "2.3.0", "react-redux": "8.0.5", "react-refresh": "0.14.0", "react-router-dom": "6.9.0", "react-select": "5.7.2", "react-spring": "9.7.1", "redux": "4.2.1", "redux-persist": "6.0.0", "stream-chain": "2.2.5", "styled-components": "5.3.9", "v8-compile-cache": "2.3.0", "winston": "3.8.2", "yargs": "17.7.1" } } ================================================ FILE: playwright.config.ts ================================================ import { PlaywrightTestConfig } from '@playwright/test'; import path from 'path'; const config: PlaywrightTestConfig = { testDir: './test', maxFailures: 0, outputDir: path.join(__dirname, 'test', 'output', 'playwright'), workers: 1, use: { screenshot: 'on', trace: 'on', }, }; export default config; ================================================ FILE: renovate.json ================================================ { "extends": [ "config:base", "group:allNonMajor", "schedule:earlyMondays" ], "git-submodules": { "enabled": true } } ================================================ FILE: scripts/prepareNodegit.js ================================================ const path = require('path'); const fs = require('fs'); const debugPath = path.join(__dirname, '..', 'node_modules', 'nodegit', 'build', 'Debug'); const mainEntry = path.join(__dirname, '..', 'node_modules', 'nodegit', 'lib', 'nodegit.js'); // First, we add an empty debug file, so we can satisfy webpack one exists fs.promises.mkdir(debugPath, { recursive: true }) .then(() => { return fs.promises.writeFile(path.join(debugPath, 'nodegit.node'), ''); }); // Then we modify the base files to not use dynamic requires fs.promises.readFile(mainEntry) .then((file) => { const data = file.toString('utf-8'); const newData = data.replace(/importExtension\(\"(.*?)\"\);/g, (match, name) => { return `try { require('./${name}'); } catch { }`; }); return fs.promises.writeFile(mainEntry, newData); }) ================================================ FILE: scripts/setupMacOSCertificates.sh ================================================ #!/usr/bin/env sh # Based on https://github.com/electron/fiddle/blob/master/tools/add-macos-cert.sh KEY_CHAIN=build.keychain MACOS_CERT_P12_FILE=certificate.p12 # Recreate the certificate from the secure environment variable echo $MACOS_CERT_P12 | base64 --decode > $MACOS_CERT_P12_FILE #create a keychain security create-keychain -p actions $KEY_CHAIN # Make the keychain the default so identities are found security default-keychain -s $KEY_CHAIN # Unlock the keychain security unlock-keychain -p actions $KEY_CHAIN security import $MACOS_CERT_P12_FILE -k $KEY_CHAIN -P $MACOS_CERT_PASSWORD -T /usr/bin/codesign; security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN # remove certs rm -fr *.p12 ================================================ FILE: src/app/assets/open-data-rights.ts ================================================ import { IconDefinition, IconName } from '@fortawesome/fontawesome-svg-core'; const faOpenDataRights: IconDefinition = { prefix: 'fab', iconName: 'open-data-rights' as IconName, icon: [ 512, 512, [], 'f1e9', 'M500.2,399.6L392.4,291.8C452.5,272.4,496,216,496,149.5C496,66.9,429.1,0,346.5,0S197,66.9,197,149.5v47.6h-47.6 C66.9,197.1,0,264,0,346.5S66.9,496,149.5,496c66.5,0,122.9-43.5,142.3-103.6l107.8,107.8c7,7,16.1,10.5,25.2,10.5 s18.3-3.5,25.2-10.5l50.2-50.2C514.2,436.2,514.2,413.5,500.2,399.6z M299,149.5c0-26.2,21.3-47.6,47.6-47.6s47.6,21.3,47.6,47.6 c0,26.2-21.3,47.6-47.6,47.6H299V149.5z M247.8,299H264v16.2L247.8,299z M230.4,427.5c-21.6,21.6-50.4,33.5-81,33.5 c-30.6,0-59.3-11.9-81-33.5c-21.6-21.6-33.5-50.4-33.5-81c0-30.6,11.9-59.3,33.5-81c21.6-21.6,50.4-33.5,81-33.5H197v49.4h-47.6 c-35.9,0-65.1,29.2-65.1,65.1s29.2,65.1,65.1,65.1s65.1-29.2,65.1-65.1v-35.9c1.7,4.7,4.5,9,8.2,12.8l40,40 C259.2,387.6,248,409.9,230.4,427.5z M179.5,316.5v30.1c0,16.6-13.5,30.1-30.1,30.1c-16.6,0-30.1-13.5-30.1-30.1 s13.5-30.1,30.1-30.1H179.5z M475.5,425.3l-50.2,50.2c-0.1,0.1-0.2,0.2-0.5,0.2s-0.4-0.1-0.5-0.2L298.9,350.1c0-1.2,0-2.4,0-3.6V299 h47.6c1.2,0,2.4,0,3.5,0l125.4,125.4c0.1,0.1,0.2,0.2,0.2,0.5C475.7,425.1,475.6,425.3,475.5,425.3z', ], }; export default faOpenDataRights; ================================================ FILE: src/app/components/App.tsx ================================================ import React, { Component } from 'react'; import styled, { StyleSheetManager } from 'styled-components'; import { HashRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; import 'app/styles'; import Pages from 'app/screens'; import store, { persistor } from 'app/store'; import Loading from './Loading'; import { RepositorySubscription } from 'app/store/data/selectors'; import { ProviderSubscription } from 'app/store/accounts/selectors'; import { EmailSubscription } from 'app/store/email/selectors'; import Tour from './Tour'; const Main = styled.main` position: relative; `; class App extends Component { componentDidMount(): void { document.getElementById('loader').style.display = 'none'; } render(): JSX.Element { return ( } persistor={persistor}> {/** Presentational components */}
{/** Subscription managers */}
); } } export default App; ================================================ FILE: src/app/components/Button.tsx ================================================ import React, { CSSProperties, HTMLAttributes, PropsWithChildren } from 'react'; import styled, { css } from 'styled-components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Ball } from './Loading'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { Link } from 'react-router-dom'; type ThemeColor = 'blue' | 'red' | 'yellow' | 'green' | 'gray'; interface ButtonProps extends HTMLAttributes { backgroundColor?: ThemeColor; fullWidth?: boolean; color?: string; } const StyledButtonStyles = css` background-color: var(--color-${(props) => props.backgroundColor || 'blue'}-500); display: flex; align-items: center; justify-content: center; color: ${(props) => props.color || 'var(--color-white)'}; height: 40px; font-size: 14px; font-weight: 500; text-decoration: none !important; font-family: var(--font-heading); border-radius: 8px; outline: 0 !important; margin: 4px 0; border: 0; padding: 0 16px; border: 1px solid var(--color-${(props) => props.backgroundColor || 'blue'}-600); overflow-wrap: break-word; letter-spacing: -0.3px; &:hover&:not(:disabled) { cursor: default; background-color: var(--color-${(props) => props.backgroundColor || 'blue'}-600); } &:disabled { background-color: var(--color-gray-300); cursor: not-allowed; color: var(--color-gray-600); border-color: var(--color-gray-400); } ${(props) => props.fullWidth && css` width: 100%; `} `; const StyledButton = styled.button` ${StyledButtonStyles} `; const Label = styled.div` flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; `; const Margin = styled.div` width: 8px; `; export const LinkButton = styled(Link)` ${StyledButtonStyles}; `; export const SimpleButton = styled.button` border: 0; margin: 0; padding: 0; background-color: inherit; color: inherit; `; export const StyledGhostButton = styled(StyledButton)` color: ${(props) => props.backgroundColor && props.backgroundColor !== 'blue' ? `var(--color-${props.backgroundColor}-500)` : 'var(--color-heading)'}; font-size: 14px; padding: 8px 16px; background-color: ${() => 'transparent'}; border: 0; font-weight: 500; &:hover&:not(:disabled) { background-color: var(--color-${(props) => props.backgroundColor || 'blue'}-50); } &:active { background-color: var(--color-${(props) => props.backgroundColor || 'blue'}-100); } `; interface Props extends ButtonProps { loading?: boolean; onClick?: (event: React.MouseEvent) => void; disabled?: boolean; icon?: IconProp; style?: CSSProperties; } const Button = ({ children, loading, onClick, disabled, icon, ...props }: PropsWithChildren): JSX.Element => { return ( {icon && !loading ? : null} {loading ? (<>) : null} ); }; export const GhostButton = ({ children, loading, onClick, disabled, icon, ...props }: PropsWithChildren): JSX.Element => { return ( {icon ? : null} {loading ? (<>) : null} ); }; export default Button; ================================================ FILE: src/app/components/Code.tsx ================================================ import styled, { css } from 'styled-components'; const Code = styled.div<{ removed?: boolean; added?: boolean; updated?: boolean }>` font-family: var(--font-mono); background-color: #f8f8f8; padding: 16px 24px; max-width: 100%; line-height: 2; white-space: pre-wrap; user-select: text; display: flex; flex-direction: column; word-break: break-all; font-size: 12px; margin: 4px 0; border-radius: 8px; h5 { margin-top: 0; opacity: 1; font-weight: 600; } &.icon { height: 1em; } ${(props) => props.added && css` background-color: var(--color-green-100); color: var(--color-green-500); `} ${(props) => props.removed && css` background-color: var(--color-red-100); color: var(--color-red-500); `} ${(props) => props.updated && css` background-color: var(--color-yellow-100); color: var(--color-yellow-500); `} `; export default Code; ================================================ FILE: src/app/components/IconBadge.tsx ================================================ import React, { PropsWithChildren } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import styled from 'styled-components'; import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; import { H2 } from './Typography'; import { PullContainer } from './Utility'; const Container = styled.div` background-color: var(--color-blue-200); color: var(--color-blue-700); display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; font-size: 2em; width: 2em; height: 2em; flex: 0 0 auto; margin-right: 0.5em; `; interface IconBadgeProps { icon: IconDefinition; } function IconBadge({ icon }: IconBadgeProps) { return ( ); } export function IconBadgeWithTitle({ children, icon }: PropsWithChildren) { return (

{children}

); } export default IconBadge; ================================================ FILE: src/app/components/Input.tsx ================================================ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faChevronDown } from '@fortawesome/free-solid-svg-icons'; import React, { useCallback } from 'react'; import styled, { css } from 'styled-components'; export const TextInput = styled.input` padding: 16px; border-radius: 4px; font-size: 16px; margin-bottom: 16px; background-color: var(--color-gray-50); border: 1px solid var(--color-border); color: var(--color-gray-800); &::placeholder { color: var(--color-gray-500); } `; export const Label = styled.label` font-size: 0.8em; display: flex; flex-direction: column; font-family: var(--font-mono); color: var(--color-gray-600); & > span { margin-left: 4px; margin-bottom: 2px; } `; const Select = styled.select<{ hasPlaceholder?: boolean }>` height: 50px; padding: 16px; border-radius: 4px; width: 100%; margin-bottom: 16px; background-color: var(--color-gray-50); border: 1px solid var(--color-border); color: inherit; appearance: none; ${(props) => props.hasPlaceholder && css` color: #00000066; `}; &:disabled { background-color: var(--color-gray-300); color: var(--color-gray-700); cursor: not-allowed; } `; const SelectContainer = styled.div` position: relative; svg { position: absolute; right: 16px; top: calc(50% - 8px); transform: translateY(-50%); } `; interface DropdownProps { label: string; options: string[] | Record; value: string; onSelect: (selectedValue: string) => void; disabled?: boolean; placeholder?: string; } type Option = { key: string, value: unknown }; export function Dropdown(props: DropdownProps): JSX.Element { const { label, options, value, disabled, placeholder, onSelect } = props; const availableOptions: Option[] = Array.isArray(options) ? options.map