Repository: NullVoxPopuli/emberclear Branch: master Commit: 92881d22cfad Files: 1168 Total size: 1.2 MB Directory structure: gitextract_de_b7zvl/ ├── .codeclimate.yml ├── .editorconfig ├── .github/ │ ├── renovate.json5 │ └── workflows/ │ ├── web-addon-crypto.yml │ ├── web-addon-encoding.yml │ ├── web-addon-local-account.yml │ ├── web-addon-networking.yml │ ├── web-addon-test-helpers.yml │ ├── web-addon-tracked-local-storage.yml │ ├── web-addon-ui.yml │ ├── web-app-deploy.yml │ ├── web-app-quality.yml │ ├── web-app-tests.yml │ ├── web-pinochle-deploy.yml │ ├── web-pinochle-quality.yml │ ├── web-pinochle-tests.yml │ └── web-smoke-tests.yml ├── .gitignore ├── .gitmodules ├── .sonarcloud.properties ├── .vscode/ │ └── launch.json ├── .whitesource ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── benchmarks/ │ ├── crypto/ │ │ ├── .babelrc.js │ │ ├── .gitignore │ │ ├── package.json │ │ ├── run │ │ ├── src/ │ │ │ ├── bench/ │ │ │ │ ├── base64.ts │ │ │ │ ├── hex.ts │ │ │ │ ├── key-generation.ts │ │ │ │ ├── nonce-generation.ts │ │ │ │ ├── round-trip-long.ts │ │ │ │ ├── round-trip.ts │ │ │ │ └── stringConvension.ts │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── js-nacl.ts │ │ │ │ ├── libsodium.ts │ │ │ │ ├── round-trip-implementations.ts │ │ │ │ ├── tweet-nacl.ts │ │ │ │ └── utils.ts │ │ │ └── utils.ts │ │ └── tsconfig.json │ └── emoji-replace/ │ ├── .babelrc.js │ ├── .gitignore │ ├── package.json │ ├── run │ ├── src/ │ │ ├── bench/ │ │ │ ├── -utils.ts │ │ │ ├── long.ts │ │ │ ├── micro.ts │ │ │ └── short.ts │ │ └── index.ts │ ├── tsconfig.json │ └── types/ │ └── emojis.d.ts ├── client/ │ ├── android-wrapper/ │ │ ├── .gitignore │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── res/ │ │ │ └── values/ │ │ │ └── styles.xml │ │ ├── build.gradle │ │ ├── enable-debug.sh │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ ├── gradle-wrapper.properties │ │ │ └── gradle.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ └── web/ │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.js │ ├── .stylelintignore │ ├── .template-lintrc.js │ ├── .vim/ │ │ └── coc-settings.json │ ├── .vscode/ │ │ └── settings.json │ ├── addons/ │ │ ├── crypto/ │ │ │ ├── .editorconfig │ │ │ ├── .ember-cli │ │ │ ├── .eslintignore │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── .template-lintrc.js │ │ │ ├── .watchmanconfig │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── addon/ │ │ │ │ ├── -private/ │ │ │ │ │ └── types.ts │ │ │ │ ├── connector.ts │ │ │ │ ├── index.ts │ │ │ │ ├── services/ │ │ │ │ │ └── workers.ts │ │ │ │ ├── tsconfig.json │ │ │ │ ├── types.ts │ │ │ │ └── workers/ │ │ │ │ └── crypto/ │ │ │ │ ├── actions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── messages.ts │ │ │ │ └── utils/ │ │ │ │ ├── array.ts │ │ │ │ ├── bip39/ │ │ │ │ │ └── wordlists/ │ │ │ │ │ ├── english.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── mnemonic.ts │ │ │ │ ├── nacl.ts │ │ │ │ ├── socket.ts │ │ │ │ └── string-encoding.ts │ │ │ ├── addon-test-support/ │ │ │ │ ├── index.ts │ │ │ │ ├── setup.ts │ │ │ │ └── tsconfig.json │ │ │ ├── app/ │ │ │ │ └── services/ │ │ │ │ └── workers.ts │ │ │ ├── config/ │ │ │ │ ├── ember-try.js │ │ │ │ └── environment.js │ │ │ ├── ember-cli-build.js │ │ │ ├── index.js │ │ │ ├── lib/ │ │ │ │ └── worker-build.js │ │ │ ├── package.json │ │ │ ├── testem.js │ │ │ ├── tests/ │ │ │ │ ├── dummy/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── app.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── environment.d.ts │ │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── models/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── router.js │ │ │ │ │ │ ├── routes/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── styles/ │ │ │ │ │ │ │ └── app.css │ │ │ │ │ │ └── templates/ │ │ │ │ │ │ └── application.hbs │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ember-cli-update.json │ │ │ │ │ │ ├── environment.js │ │ │ │ │ │ ├── optional-features.json │ │ │ │ │ │ └── targets.js │ │ │ │ │ └── public/ │ │ │ │ │ └── robots.txt │ │ │ │ ├── helpers/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── index.html │ │ │ │ ├── integration/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── test-helper.ts │ │ │ │ ├── tsconfig.json │ │ │ │ ├── unit/ │ │ │ │ │ └── services/ │ │ │ │ │ └── workers-test.js │ │ │ │ └── workers/ │ │ │ │ └── crypto/ │ │ │ │ ├── mnemonic-test.ts │ │ │ │ ├── nacl-test.ts │ │ │ │ └── string-encoding-test.ts │ │ │ ├── tsconfig.compiler-options.json │ │ │ ├── tsconfig.json │ │ │ ├── types/ │ │ │ │ └── overrides.d.ts │ │ │ └── vendor/ │ │ │ └── .gitkeep │ │ ├── encoding/ │ │ │ ├── .editorconfig │ │ │ ├── .ember-cli │ │ │ ├── .eslintignore │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── .template-lintrc.js │ │ │ ├── .travis.yml │ │ │ ├── .watchmanconfig │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── addon/ │ │ │ │ ├── string.ts │ │ │ │ └── tsconfig.json │ │ │ ├── config/ │ │ │ │ ├── ember-try.js │ │ │ │ └── environment.js │ │ │ ├── ember-cli-build.js │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ ├── testem.js │ │ │ ├── tests/ │ │ │ │ ├── dummy/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── app.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── environment.d.ts │ │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── models/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── router.js │ │ │ │ │ │ ├── routes/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── styles/ │ │ │ │ │ │ │ └── app.css │ │ │ │ │ │ └── templates/ │ │ │ │ │ │ └── application.hbs │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ember-cli-update.json │ │ │ │ │ │ ├── environment.js │ │ │ │ │ │ ├── optional-features.json │ │ │ │ │ │ └── targets.js │ │ │ │ │ └── public/ │ │ │ │ │ └── robots.txt │ │ │ │ ├── helpers/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── index.html │ │ │ │ ├── integration/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── test-helper.js │ │ │ │ ├── tsconfig.json │ │ │ │ └── unit/ │ │ │ │ ├── .gitkeep │ │ │ │ └── string-test.ts │ │ │ ├── tsconfig.compiler-options.json │ │ │ ├── tsconfig.json │ │ │ └── vendor/ │ │ │ └── .gitkeep │ │ ├── local-account/ │ │ │ ├── .editorconfig │ │ │ ├── .ember-cli │ │ │ ├── .eslintignore │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── .template-lintrc.js │ │ │ ├── .travis.yml │ │ │ ├── .watchmanconfig │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── addon/ │ │ │ │ ├── adapters/ │ │ │ │ │ └── application.js │ │ │ │ ├── index.ts │ │ │ │ ├── models/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── channel-context-chain.ts │ │ │ │ │ ├── channel.ts │ │ │ │ │ ├── contact.ts │ │ │ │ │ ├── identity.ts │ │ │ │ │ ├── user.ts │ │ │ │ │ ├── vote-chain.ts │ │ │ │ │ └── vote.ts │ │ │ │ ├── serializers/ │ │ │ │ │ └── application.js │ │ │ │ ├── services/ │ │ │ │ │ ├── channel-manager.ts │ │ │ │ │ ├── contact-manager.ts │ │ │ │ │ └── current-user.ts │ │ │ │ ├── tsconfig.json │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── addon-test-support/ │ │ │ │ ├── -private/ │ │ │ │ │ ├── contact.ts │ │ │ │ │ ├── current-user.ts │ │ │ │ │ ├── storage.ts │ │ │ │ │ ├── user.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── index.ts │ │ │ │ └── tsconfig.json │ │ │ ├── app/ │ │ │ │ ├── adapters/ │ │ │ │ │ └── application.js │ │ │ │ ├── models/ │ │ │ │ │ ├── channel-context-chain.js │ │ │ │ │ ├── channel.js │ │ │ │ │ ├── contact.js │ │ │ │ │ ├── identity.js │ │ │ │ │ ├── user.js │ │ │ │ │ ├── vote-chain.js │ │ │ │ │ └── vote.js │ │ │ │ ├── serializers/ │ │ │ │ │ └── application.js │ │ │ │ └── services/ │ │ │ │ ├── channel-manager.js │ │ │ │ ├── contact-manager.js │ │ │ │ ├── current-user.js │ │ │ │ └── store.js │ │ │ ├── config/ │ │ │ │ ├── ember-try.js │ │ │ │ └── environment.js │ │ │ ├── ember-cli-build.js │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ ├── testem.js │ │ │ ├── tests/ │ │ │ │ ├── dummy/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── app.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── environment.d.ts │ │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── models/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── router.js │ │ │ │ │ │ ├── routes/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── styles/ │ │ │ │ │ │ │ └── app.css │ │ │ │ │ │ └── templates/ │ │ │ │ │ │ └── application.hbs │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ember-cli-update.json │ │ │ │ │ │ ├── environment.js │ │ │ │ │ │ ├── optional-features.json │ │ │ │ │ │ └── targets.js │ │ │ │ │ └── public/ │ │ │ │ │ └── robots.txt │ │ │ │ ├── helpers/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── index.html │ │ │ │ ├── integration/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── test-helper.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── unit/ │ │ │ │ ├── create-current-user-test.ts │ │ │ │ ├── models/ │ │ │ │ │ ├── contact-test.ts │ │ │ │ │ └── user-test.ts │ │ │ │ └── services/ │ │ │ │ └── current-user-test.ts │ │ │ ├── tsconfig.compiler-options.json │ │ │ ├── tsconfig.json │ │ │ ├── types/ │ │ │ │ └── overrides.d.ts │ │ │ └── vendor/ │ │ │ └── .gitkeep │ │ ├── networking/ │ │ │ ├── .editorconfig │ │ │ ├── .ember-cli │ │ │ ├── .eslintignore │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── .template-lintrc.js │ │ │ ├── .watchmanconfig │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── addon/ │ │ │ │ ├── errors.ts │ │ │ │ ├── index.ts │ │ │ │ ├── models/ │ │ │ │ │ ├── message/ │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── message.ts │ │ │ │ │ └── relay.ts │ │ │ │ ├── required-data.ts │ │ │ │ ├── services/ │ │ │ │ │ ├── connection/ │ │ │ │ │ │ ├── ephemeral/ │ │ │ │ │ │ │ └── ephemeral-connection.ts │ │ │ │ │ │ ├── manager.ts │ │ │ │ │ │ └── status.ts │ │ │ │ │ ├── connection.ts │ │ │ │ │ ├── contacts/ │ │ │ │ │ │ └── online-checker.ts │ │ │ │ │ ├── messages/ │ │ │ │ │ │ ├── -utils/ │ │ │ │ │ │ │ └── builder.ts │ │ │ │ │ │ ├── auto-responder.ts │ │ │ │ │ │ ├── dispatcher.ts │ │ │ │ │ │ ├── factory.ts │ │ │ │ │ │ ├── handler.ts │ │ │ │ │ │ └── processor.ts │ │ │ │ │ └── status-manager.ts │ │ │ │ ├── tsconfig.json │ │ │ │ ├── type-support.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils/ │ │ │ │ └── connection/ │ │ │ │ ├── connection-pool.ts │ │ │ │ └── connection.ts │ │ │ ├── addon-test-support/ │ │ │ │ ├── index.ts │ │ │ │ └── tsconfig.json │ │ │ ├── app/ │ │ │ │ ├── models/ │ │ │ │ │ ├── message.js │ │ │ │ │ └── relay.js │ │ │ │ └── services/ │ │ │ │ ├── connection/ │ │ │ │ │ ├── manager.js │ │ │ │ │ └── status.js │ │ │ │ ├── connection.js │ │ │ │ ├── contacts/ │ │ │ │ │ └── online-checker.js │ │ │ │ ├── messages/ │ │ │ │ │ ├── auto-responder.js │ │ │ │ │ ├── dispatcher.js │ │ │ │ │ ├── factory.js │ │ │ │ │ ├── handler.js │ │ │ │ │ └── processor.js │ │ │ │ └── status-manager.js │ │ │ ├── config/ │ │ │ │ ├── ember-try.js │ │ │ │ └── environment.js │ │ │ ├── ember-cli-build.js │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ ├── testem.js │ │ │ ├── tests/ │ │ │ │ ├── dummy/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── app.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── environment.d.ts │ │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── models/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── router.ts │ │ │ │ │ │ ├── routes/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── styles/ │ │ │ │ │ │ │ └── app.css │ │ │ │ │ │ ├── templates/ │ │ │ │ │ │ │ └── application.hbs │ │ │ │ │ │ └── with-test-waiter.js │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ember-cli-update.json │ │ │ │ │ │ ├── environment.js │ │ │ │ │ │ ├── optional-features.json │ │ │ │ │ │ └── targets.js │ │ │ │ │ └── public/ │ │ │ │ │ └── robots.txt │ │ │ │ ├── helpers/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── index.html │ │ │ │ ├── integration/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── test-helper.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── unit/ │ │ │ │ └── services/ │ │ │ │ ├── connection/ │ │ │ │ │ └── status-test.js │ │ │ │ └── messages/ │ │ │ │ ├── auto-responder-test.ts │ │ │ │ ├── handler-test.ts │ │ │ │ └── utils/ │ │ │ │ └── -encryption-test.ts │ │ │ ├── tsconfig.compiler-options.json │ │ │ ├── tsconfig.json │ │ │ ├── types/ │ │ │ │ └── overrides.d.ts │ │ │ └── vendor/ │ │ │ └── .gitkeep │ │ ├── prism/ │ │ │ └── README.md │ │ ├── test-helpers/ │ │ │ ├── .editorconfig │ │ │ ├── .ember-cli │ │ │ ├── .eslintignore │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── .template-lintrc.js │ │ │ ├── .travis.yml │ │ │ ├── .watchmanconfig │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── addon-test-support/ │ │ │ │ ├── -private/ │ │ │ │ │ ├── get-service.ts │ │ │ │ │ ├── get-store.ts │ │ │ │ │ ├── refresh.ts │ │ │ │ │ ├── setup-router.ts │ │ │ │ │ ├── stub-service.ts │ │ │ │ │ ├── visit.ts │ │ │ │ │ └── wait-until.ts │ │ │ │ ├── index.ts │ │ │ │ └── tsconfig.json │ │ │ ├── app/ │ │ │ │ └── .gitkeep │ │ │ ├── config/ │ │ │ │ ├── ember-try.js │ │ │ │ └── environment.js │ │ │ ├── ember-cli-build.js │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ ├── testem.js │ │ │ ├── tests/ │ │ │ │ ├── dummy/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── app.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── environment.d.ts │ │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── models/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── router.js │ │ │ │ │ │ ├── routes/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── styles/ │ │ │ │ │ │ │ └── app.css │ │ │ │ │ │ └── templates/ │ │ │ │ │ │ └── application.hbs │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ember-cli-update.json │ │ │ │ │ │ ├── environment.js │ │ │ │ │ │ ├── optional-features.json │ │ │ │ │ │ └── targets.js │ │ │ │ │ └── public/ │ │ │ │ │ └── robots.txt │ │ │ │ ├── helpers/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── index.html │ │ │ │ ├── integration/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── test-helper.js │ │ │ │ └── unit/ │ │ │ │ └── .gitkeep │ │ │ ├── tsconfig.compiler-options.json │ │ │ ├── tsconfig.json │ │ │ ├── types/ │ │ │ │ └── overrides.d.ts │ │ │ └── vendor/ │ │ │ └── .gitkeep │ │ ├── tracked-local-storage/ │ │ │ ├── .editorconfig │ │ │ ├── .ember-cli │ │ │ ├── .eslintignore │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── .template-lintrc.js │ │ │ ├── .watchmanconfig │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── addon/ │ │ │ │ ├── index.ts │ │ │ │ └── tsconfig.json │ │ │ ├── config/ │ │ │ │ ├── ember-try.js │ │ │ │ └── environment.js │ │ │ ├── ember-cli-build.js │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ ├── testem.js │ │ │ ├── tests/ │ │ │ │ ├── dummy/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── app.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── environment.d.ts │ │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── models/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── router.js │ │ │ │ │ │ ├── routes/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── styles/ │ │ │ │ │ │ │ └── app.css │ │ │ │ │ │ └── templates/ │ │ │ │ │ │ └── application.hbs │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ember-cli-update.json │ │ │ │ │ │ ├── environment.js │ │ │ │ │ │ ├── optional-features.json │ │ │ │ │ │ └── targets.js │ │ │ │ │ └── public/ │ │ │ │ │ └── robots.txt │ │ │ │ ├── index.html │ │ │ │ ├── test-helper.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── unit/ │ │ │ │ └── in-local-storage-test.ts │ │ │ ├── tsconfig.compiler-options.json │ │ │ ├── tsconfig.json │ │ │ └── vendor/ │ │ │ └── .gitkeep │ │ ├── tsconfig.json │ │ └── ui/ │ │ ├── .editorconfig │ │ ├── .ember-cli │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .template-lintrc.js │ │ ├── README.md │ │ ├── addon/ │ │ │ ├── components/ │ │ │ │ ├── backdrop.hbs │ │ │ │ ├── collapsible/ │ │ │ │ │ ├── icon.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── dropdown/ │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── ellipsis-loader.hbs │ │ │ │ ├── external-link.hbs │ │ │ │ ├── field.ts │ │ │ │ ├── focus-card.hbs │ │ │ │ ├── hamburger-button.hbs │ │ │ │ ├── hover-tip.hbs │ │ │ │ ├── media-info-card.ts │ │ │ │ ├── modal.hbs │ │ │ │ └── switch.ts │ │ │ ├── helpers/ │ │ │ │ ├── and.ts │ │ │ │ ├── eq.ts │ │ │ │ ├── not.ts │ │ │ │ └── prevent-default.ts │ │ │ └── tsconfig.json │ │ ├── addon-test-support/ │ │ │ ├── key-events.ts │ │ │ ├── page-objects.ts │ │ │ └── tsconfig.json │ │ ├── app/ │ │ │ ├── components/ │ │ │ │ ├── backdrop.js │ │ │ │ ├── collapsible/ │ │ │ │ │ └── icon.js │ │ │ │ ├── collapsible.js │ │ │ │ ├── dropdown.js │ │ │ │ ├── ellipsis-loader.js │ │ │ │ ├── external-link.js │ │ │ │ ├── field.js │ │ │ │ ├── focus-card.js │ │ │ │ ├── hamburger-button.js │ │ │ │ ├── hover-tip.js │ │ │ │ ├── media-info-card.js │ │ │ │ ├── modal.js │ │ │ │ └── switch.js │ │ │ ├── helpers/ │ │ │ │ ├── and.js │ │ │ │ ├── eq.js │ │ │ │ ├── not.js │ │ │ │ └── prevent-default.js │ │ │ └── styles/ │ │ │ └── @emberclear/ │ │ │ ├── animation.css │ │ │ ├── buttons.css │ │ │ ├── defaults.css │ │ │ ├── layout.css │ │ │ ├── mixins.css │ │ │ ├── shoelace-overrides.css │ │ │ ├── sizes.css │ │ │ ├── spacing.css │ │ │ ├── themes/ │ │ │ │ ├── default.css │ │ │ │ └── midnight.css │ │ │ └── ui.css │ │ ├── config/ │ │ │ └── environment.js │ │ ├── ember-cli-build.js │ │ ├── index.js │ │ ├── package.json │ │ ├── testem.js │ │ ├── tests/ │ │ │ ├── dummy/ │ │ │ │ ├── app/ │ │ │ │ │ ├── app.ts │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── config/ │ │ │ │ │ │ └── environment.d.ts │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── index.html │ │ │ │ │ ├── models/ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── router.js │ │ │ │ │ ├── routes/ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── styles/ │ │ │ │ │ │ └── app.css │ │ │ │ │ └── templates/ │ │ │ │ │ └── application.hbs │ │ │ │ ├── config/ │ │ │ │ │ ├── environment.js │ │ │ │ │ ├── optional-features.json │ │ │ │ │ └── targets.js │ │ │ │ └── public/ │ │ │ │ └── robots.txt │ │ │ ├── helpers/ │ │ │ │ └── .gitkeep │ │ │ ├── index.html │ │ │ ├── integration/ │ │ │ │ ├── .gitkeep │ │ │ │ ├── components/ │ │ │ │ │ ├── backdrop-test.js │ │ │ │ │ ├── collapsible-test.js │ │ │ │ │ ├── dropdown-test.js │ │ │ │ │ ├── ellipsis-loader-test.js │ │ │ │ │ ├── external-link-test.js │ │ │ │ │ ├── field-test.js │ │ │ │ │ ├── focus-card-test.ts │ │ │ │ │ ├── hamburger-button-test.js │ │ │ │ │ ├── hover-tip-test.js │ │ │ │ │ ├── media-info-card-test.ts │ │ │ │ │ ├── modal-test.ts │ │ │ │ │ └── switch-test.ts │ │ │ │ └── helpers/ │ │ │ │ ├── and-test.ts │ │ │ │ ├── eq-test.ts │ │ │ │ └── not-test.ts │ │ │ ├── test-helper.ts │ │ │ ├── tsconfig.json │ │ │ └── unit/ │ │ │ └── .gitkeep │ │ ├── tsconfig.compiler-options.json │ │ ├── tsconfig.json │ │ ├── types/ │ │ │ ├── dummy/ │ │ │ │ └── index.d.ts │ │ │ ├── global.d.ts │ │ │ └── overrides.d.ts │ │ └── vendor/ │ │ └── .gitkeep │ ├── config/ │ │ ├── .eslintrc.js │ │ ├── .template-lintrc.js │ │ ├── package.json │ │ ├── testem.js │ │ ├── tsconfig.compiler-options.json │ │ └── utils/ │ │ ├── ember-build.js │ │ └── log.js │ ├── emberclear/ │ │ ├── .dockerignore │ │ ├── .editorconfig │ │ ├── .ember-cli │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .template-lintrc.js │ │ ├── Dockerfile │ │ ├── Dockerfile.release │ │ ├── README.md │ │ ├── app/ │ │ │ ├── app.ts │ │ │ ├── components/ │ │ │ │ ├── app/ │ │ │ │ │ ├── app-shell-remover.ts │ │ │ │ │ ├── container/ │ │ │ │ │ │ ├── hero.hbs │ │ │ │ │ │ ├── main.hbs │ │ │ │ │ │ └── primary-hero.hbs │ │ │ │ │ ├── modals.hbs │ │ │ │ │ ├── off-canvas/ │ │ │ │ │ │ ├── -page.ts │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── sidebar/ │ │ │ │ │ │ ├── actions/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── response-action/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ └── response-panel/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── chats/ │ │ │ │ │ │ │ ├── -page.ts │ │ │ │ │ │ │ ├── channel/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── channel-form/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── contact/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── data/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── list.hbs │ │ │ │ │ │ │ └── offline-counter.hbs │ │ │ │ │ │ ├── footer.hbs │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── unread-indicator.hbs │ │ │ │ │ ├── top-nav/ │ │ │ │ │ │ ├── connection-status/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── current-chat-name/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── install/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── locale-select/ │ │ │ │ │ │ │ ├── -page.ts │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── user-drop-menu/ │ │ │ │ │ │ ├── -page.ts │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── update-checker.ts │ │ │ │ ├── app-footer.hbs │ │ │ │ ├── copy-text-button.ts │ │ │ │ ├── embedded-media.hbs │ │ │ │ ├── error-card/ │ │ │ │ │ └── index.hbs │ │ │ │ ├── fetch-open-graph.ts │ │ │ │ ├── file-chooser/ │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── keyboard-shortcuts/ │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── key/ │ │ │ │ │ └── index.hbs │ │ │ │ ├── mnemonic-display.ts │ │ │ │ ├── modal-static/ │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── pod/ │ │ │ │ │ ├── add-friend/ │ │ │ │ │ │ └── add-contact/ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── chat/ │ │ │ │ │ │ ├── chat-entry/ │ │ │ │ │ │ │ ├── embeds-menu/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── snippet/ │ │ │ │ │ │ │ │ ├── -page.ts │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── chat-history/ │ │ │ │ │ │ ├── connection-status/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ ├── message/ │ │ │ │ │ │ │ ├── delivery-confirmations/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── embedded-resource/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── metadata-preview/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── header/ │ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── linked-media.hbs │ │ │ │ │ │ ├── new-messages/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── notification-prompt/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── unread-management/ │ │ │ │ │ │ ├── -page.ts │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── contacts/ │ │ │ │ │ │ ├── contact-table/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── header/ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── faq/ │ │ │ │ │ │ └── q-and-a.hbs │ │ │ │ │ ├── index/ │ │ │ │ │ │ ├── begin-button.ts │ │ │ │ │ │ └── compatibility/ │ │ │ │ │ │ ├── -utils/ │ │ │ │ │ │ │ └── detection.ts │ │ │ │ │ │ ├── feature/ │ │ │ │ │ │ │ ├── boolean-icon.hbs │ │ │ │ │ │ │ └── index.hbs │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── -machine.ts │ │ │ │ │ │ ├── login-form/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── transfer-prompt/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── template.hbs │ │ │ │ │ ├── q-r/ │ │ │ │ │ │ ├── -machine.ts │ │ │ │ │ │ ├── -types.ts │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── login/ │ │ │ │ │ │ └── ask.hbs │ │ │ │ │ ├── settings/ │ │ │ │ │ │ ├── danger-zone/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── interface/ │ │ │ │ │ │ │ ├── -page.ts │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── navigation.hbs │ │ │ │ │ │ ├── profile/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── relays/ │ │ │ │ │ │ ├── new-relay-form/ │ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── relay-table/ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── row/ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── setup/ │ │ │ │ │ ├── -machine.ts │ │ │ │ │ ├── completed/ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── creating/ │ │ │ │ │ │ ├── index.hbs │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.hbs │ │ │ │ │ ├── index.ts │ │ │ │ │ └── overwrite.hbs │ │ │ │ ├── q-r-code.hbs │ │ │ │ ├── q-r-scanner/ │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── search/ │ │ │ │ │ ├── -page.ts │ │ │ │ │ ├── index.hbs │ │ │ │ │ ├── index.ts │ │ │ │ │ └── result.hbs │ │ │ │ └── status-icon/ │ │ │ │ ├── index.hbs │ │ │ │ └── index.ts │ │ │ ├── config/ │ │ │ │ └── environment.d.ts │ │ │ ├── controllers/ │ │ │ │ ├── application.ts │ │ │ │ ├── chat/ │ │ │ │ │ ├── in-channel.ts │ │ │ │ │ └── privately-with.ts │ │ │ │ ├── invite.ts │ │ │ │ └── logout.ts │ │ │ ├── formats.js │ │ │ ├── helpers/ │ │ │ │ ├── first-8.ts │ │ │ │ ├── handle-sidebar-click.ts │ │ │ │ ├── has-feature-flag.ts │ │ │ │ ├── includes.ts │ │ │ │ ├── is-channel.js │ │ │ │ ├── is-contact.js │ │ │ │ ├── is-current-user.js │ │ │ │ ├── is-present.ts │ │ │ │ ├── or.ts │ │ │ │ ├── queue.ts │ │ │ │ └── sub.ts │ │ │ ├── index.html │ │ │ ├── models/ │ │ │ │ ├── action.ts │ │ │ │ ├── invitation-result.ts │ │ │ │ ├── invitation.ts │ │ │ │ └── message-media.ts │ │ │ ├── modifiers/ │ │ │ │ ├── format-code.ts │ │ │ │ ├── has-unread.ts │ │ │ │ ├── maybe-nudge-to-bottom.ts │ │ │ │ ├── message-scroll-listener.ts │ │ │ │ ├── qr-image.ts │ │ │ │ ├── read-watcher.ts │ │ │ │ ├── unread-message-list-observer.ts │ │ │ │ ├── unread-messages-intersection-observer.ts │ │ │ │ └── update-document-title.ts │ │ │ ├── router.js │ │ │ ├── routes/ │ │ │ │ ├── add-friend.ts │ │ │ │ ├── application.ts │ │ │ │ ├── chat/ │ │ │ │ │ ├── in-channel.ts │ │ │ │ │ └── privately-with.ts │ │ │ │ ├── chat.ts │ │ │ │ ├── contacts.ts │ │ │ │ ├── invite.ts │ │ │ │ ├── login.ts │ │ │ │ ├── logout.ts │ │ │ │ ├── qr.js │ │ │ │ ├── settings/ │ │ │ │ │ └── relays.ts │ │ │ │ ├── settings.ts │ │ │ │ └── setup.ts │ │ │ ├── services/ │ │ │ │ ├── channels/ │ │ │ │ │ ├── -utils/ │ │ │ │ │ │ ├── channel-factory.ts │ │ │ │ │ │ └── vote-sorter.ts │ │ │ │ │ ├── channel-verifier.ts │ │ │ │ │ └── vote-verifier.ts │ │ │ │ ├── chat-scroller.ts │ │ │ │ ├── connection/ │ │ │ │ │ └── ephemeral/ │ │ │ │ │ └── login/ │ │ │ │ │ ├── receive-data.ts │ │ │ │ │ └── send-data.ts │ │ │ │ ├── current-chat.ts │ │ │ │ ├── locale.ts │ │ │ │ ├── modals.ts │ │ │ │ ├── notifications.ts │ │ │ │ ├── prism-manager.ts │ │ │ │ ├── qr-manager.ts │ │ │ │ ├── redirect-manager.ts │ │ │ │ ├── session.ts │ │ │ │ ├── settings.ts │ │ │ │ ├── sidebar.ts │ │ │ │ ├── toast.ts │ │ │ │ └── window.ts │ │ │ ├── styles/ │ │ │ │ ├── app.css │ │ │ │ ├── components/ │ │ │ │ │ ├── backdrop.css │ │ │ │ │ ├── card.css │ │ │ │ │ ├── chat-entry.css │ │ │ │ │ ├── connection-status.css │ │ │ │ │ ├── contacts.css │ │ │ │ │ ├── dismissable-warning.css │ │ │ │ │ ├── ellipses-loader.css │ │ │ │ │ ├── embedded-resource.css │ │ │ │ │ ├── hover-tip.css │ │ │ │ │ ├── key.css │ │ │ │ │ ├── logout.css │ │ │ │ │ ├── message.css │ │ │ │ │ ├── messages.css │ │ │ │ │ ├── metadata-preview.css │ │ │ │ │ ├── modal.css │ │ │ │ │ ├── notification-prompt.css │ │ │ │ │ ├── q-r-code.css │ │ │ │ │ ├── q-r-scanner.css │ │ │ │ │ ├── search.css │ │ │ │ │ ├── settings-nav.css │ │ │ │ │ ├── sidebar-contact.css │ │ │ │ │ ├── sidebar-nav.css │ │ │ │ │ ├── sidebar.css │ │ │ │ │ ├── snippet.css │ │ │ │ │ ├── toastify-overrides.css │ │ │ │ │ ├── top-nav.css │ │ │ │ │ └── unread-management.css │ │ │ │ └── utility/ │ │ │ │ ├── height.css │ │ │ │ └── transitions.css │ │ │ ├── templates/ │ │ │ │ ├── add-friend.hbs │ │ │ │ ├── application.hbs │ │ │ │ ├── chat/ │ │ │ │ │ ├── in-channel.hbs │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── privately-with.hbs │ │ │ │ ├── chat.hbs │ │ │ │ ├── contacts.hbs │ │ │ │ ├── faq.hbs │ │ │ │ ├── index.hbs │ │ │ │ ├── invite.hbs │ │ │ │ ├── login.hbs │ │ │ │ ├── logout.hbs │ │ │ │ ├── not-found.hbs │ │ │ │ ├── qr.hbs │ │ │ │ ├── settings/ │ │ │ │ │ ├── danger-zone.hbs │ │ │ │ │ ├── index.hbs │ │ │ │ │ ├── interface.hbs │ │ │ │ │ └── relays.hbs │ │ │ │ ├── settings.hbs │ │ │ │ └── setup.hbs │ │ │ ├── tsconfig.json │ │ │ └── utils/ │ │ │ ├── README.md │ │ │ ├── breakpoints.ts │ │ │ ├── dom/ │ │ │ │ ├── css.ts │ │ │ │ └── utils.ts │ │ │ ├── ember-concurrency.ts │ │ │ ├── errors.ts │ │ │ ├── identity-comparison.ts │ │ │ ├── normalized-meta.ts │ │ │ ├── route-matchers.ts │ │ │ ├── string/ │ │ │ │ └── utils.ts │ │ │ └── uint8array-equality.ts │ │ ├── config/ │ │ │ ├── addons.js │ │ │ ├── build/ │ │ │ │ ├── addons.js │ │ │ │ └── static.js │ │ │ ├── coverage.js │ │ │ ├── dependency-lint.js │ │ │ ├── ember-intl.js │ │ │ ├── emberclear-local.crt │ │ │ ├── emberclear-local.csr │ │ │ ├── emberclear-local.key │ │ │ ├── environment.js │ │ │ ├── icons.js │ │ │ ├── manifest.js │ │ │ ├── netlify/ │ │ │ │ └── _redirects │ │ │ ├── optional-features.json │ │ │ └── targets.js │ │ ├── ember-cli-build.js │ │ ├── jsconfig.json │ │ ├── package.json │ │ ├── public/ │ │ │ ├── .well-known/ │ │ │ │ └── assetlinks.json │ │ │ ├── assets/ │ │ │ │ └── images/ │ │ │ │ ├── .gitkeep │ │ │ │ └── icons/ │ │ │ │ └── .gitkeep │ │ │ ├── bundle.html │ │ │ └── robots.txt │ │ ├── scripts/ │ │ │ ├── analyze-broccoli.js │ │ │ ├── analyze.sh │ │ │ ├── docker/ │ │ │ │ ├── nginx.conf │ │ │ │ └── run-nginx.sh │ │ │ ├── generate-self-signed-cert.sh │ │ │ ├── node_modules-to-ramdisk.sh │ │ │ └── test-with-coverage.sh │ │ ├── testem.js │ │ ├── tests/ │ │ │ ├── -temp/ │ │ │ │ └── qunit-xstate-test.ts │ │ │ ├── acceptance/ │ │ │ │ ├── chat/ │ │ │ │ │ ├── -unread-acceptance-test.ts │ │ │ │ │ ├── acceptance-test.ts │ │ │ │ │ ├── in-channel/ │ │ │ │ │ │ └── -acceptance-test.ts │ │ │ │ │ └── privately-with/ │ │ │ │ │ ├── -acceptance-test.ts │ │ │ │ │ └── format-code-test.ts │ │ │ │ ├── compatibility-test.ts │ │ │ │ ├── contacts/ │ │ │ │ │ └── acceptance-test.ts │ │ │ │ ├── invite/ │ │ │ │ │ └── acceptance-test.ts │ │ │ │ ├── login/ │ │ │ │ │ └── acceptance-test.ts │ │ │ │ ├── logout/ │ │ │ │ │ └── acceptance-test.ts │ │ │ │ ├── logout-test.ts │ │ │ │ ├── navigation-scroll-to-top-test.ts │ │ │ │ ├── notification-permission-prompt-test.ts │ │ │ │ ├── notifications-test.ts │ │ │ │ ├── qr/ │ │ │ │ │ └── login/ │ │ │ │ │ ├── receiver-test.ts │ │ │ │ │ └── sender-test.ts │ │ │ │ ├── search-test.ts │ │ │ │ ├── settings/ │ │ │ │ │ ├── acceptance-test.ts │ │ │ │ │ ├── danger-zone/ │ │ │ │ │ │ └── -acceptance-test.ts │ │ │ │ │ ├── interface/ │ │ │ │ │ │ └── -acceptance-test.ts │ │ │ │ │ └── relays/ │ │ │ │ │ └── -acceptance-test.ts │ │ │ │ ├── setup/ │ │ │ │ │ └── acceptance-test.ts │ │ │ │ ├── sidebar-test.ts │ │ │ │ ├── sidebar-visibility-test.ts │ │ │ │ └── update-banner-test.ts │ │ │ ├── helpers/ │ │ │ │ ├── factories/ │ │ │ │ │ ├── channel-factory.ts │ │ │ │ │ └── message-factory.ts │ │ │ │ ├── index.ts │ │ │ │ ├── page-objects.ts │ │ │ │ ├── pages/ │ │ │ │ │ ├── app.ts │ │ │ │ │ ├── chat.ts │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── chat.ts │ │ │ │ │ ├── contacts.ts │ │ │ │ │ ├── login.ts │ │ │ │ │ ├── logout.ts │ │ │ │ │ ├── qr.ts │ │ │ │ │ ├── settings.ts │ │ │ │ │ ├── setup.ts │ │ │ │ │ └── toast.ts │ │ │ │ ├── setup-relay-connection-mocks.ts │ │ │ │ ├── setup-test.ts │ │ │ │ └── track-async-data.ts │ │ │ ├── index.html │ │ │ ├── integration/ │ │ │ │ ├── components/ │ │ │ │ │ ├── embedded-media-test.js │ │ │ │ │ ├── error-card-test.js │ │ │ │ │ ├── fetch-open-graph-test.js │ │ │ │ │ ├── pod/ │ │ │ │ │ │ ├── application/ │ │ │ │ │ │ │ └── top-nav/ │ │ │ │ │ │ │ └── locale-select-test.ts │ │ │ │ │ │ └── chat/ │ │ │ │ │ │ ├── chat-entry/ │ │ │ │ │ │ │ ├── embeds-menu/ │ │ │ │ │ │ │ │ └── component-test.ts │ │ │ │ │ │ │ └── emoji-test.ts │ │ │ │ │ │ └── chat-history/ │ │ │ │ │ │ ├── message/ │ │ │ │ │ │ │ ├── embedded-resource-test.ts │ │ │ │ │ │ │ └── metadata-preview-test.ts │ │ │ │ │ │ └── new-messages-test.js │ │ │ │ │ └── settings/ │ │ │ │ │ └── interface-test.ts │ │ │ │ ├── pods/ │ │ │ │ │ └── components/ │ │ │ │ │ ├── collapsible/ │ │ │ │ │ │ └── component-test.ts │ │ │ │ │ ├── copy-text-button/ │ │ │ │ │ │ └── component-test.ts │ │ │ │ │ ├── keyboard-shortcuts/ │ │ │ │ │ │ └── component-test.ts │ │ │ │ │ ├── modal-static/ │ │ │ │ │ │ └── component-test.ts │ │ │ │ │ ├── q-r-scanner/ │ │ │ │ │ │ └── component-test.ts │ │ │ │ │ └── status-icon/ │ │ │ │ │ └── component-test.ts │ │ │ │ └── routing/ │ │ │ │ └── feature-flags-test.ts │ │ │ ├── test-helper.ts │ │ │ ├── tsconfig.json │ │ │ └── unit/ │ │ │ ├── routes/ │ │ │ │ ├── add-friend/ │ │ │ │ │ └── route-test.ts │ │ │ │ ├── chat/ │ │ │ │ │ └── route-unit-test.ts │ │ │ │ └── qr-test.js │ │ │ ├── service/ │ │ │ │ ├── channels/ │ │ │ │ │ ├── channel-verifier-test.ts │ │ │ │ │ ├── utils/ │ │ │ │ │ │ └── -vote-sorter-test.ts │ │ │ │ │ └── vote-verifier-test.ts │ │ │ │ ├── contacts/ │ │ │ │ │ └── online-checker-test.ts │ │ │ │ ├── notifications-test.ts │ │ │ │ └── redirect-manager-test.ts │ │ │ ├── services/ │ │ │ │ ├── session-test.js │ │ │ │ └── store-test.js │ │ │ └── utils/ │ │ │ ├── dom-test.ts │ │ │ └── string-test.ts │ │ ├── translations/ │ │ │ ├── de-DE.yaml │ │ │ ├── en-AU.yaml │ │ │ ├── en-us.yaml │ │ │ ├── es-ES.yaml │ │ │ ├── fr-FR.yaml │ │ │ ├── ko-KR.yaml │ │ │ ├── pt-PT.yaml │ │ │ └── ru-RU.yaml │ │ ├── tsconfig.compiler-options.json │ │ ├── tsconfig.json │ │ ├── types/ │ │ │ ├── dom-purify.d.ts │ │ │ ├── ember-a11y-testing/ │ │ │ │ └── test-support/ │ │ │ │ └── audit-if.d.ts │ │ │ ├── ember-cli-clipboard/ │ │ │ │ └── test-support/ │ │ │ │ └── index.d.ts │ │ │ ├── ember-could-get-used-to-this.d.ts │ │ │ ├── ember-intl/ │ │ │ │ └── services/ │ │ │ │ └── intl.d.ts │ │ │ ├── ember-localforage-adapter/ │ │ │ │ ├── adapters/ │ │ │ │ │ └── localforage.d.ts │ │ │ │ └── serializers/ │ │ │ │ └── localforage.d.ts │ │ │ ├── ember-modifier.d.ts │ │ │ ├── ember-service-worker-update-notify/ │ │ │ │ └── test-support/ │ │ │ │ └── updater.d.ts │ │ │ ├── ember-usable.d.ts │ │ │ ├── emberclear/ │ │ │ │ ├── addon-services.d.ts │ │ │ │ ├── ember-data.d.ts │ │ │ │ └── index.d.ts │ │ │ ├── emojis.d.ts │ │ │ ├── global.d.ts │ │ │ ├── index.d.ts │ │ │ ├── overrides.d.ts │ │ │ ├── prismjs/ │ │ │ │ └── index.d.ts │ │ │ ├── qr-scanner.d.ts │ │ │ ├── qunit-xstate-test.d.ts │ │ │ └── toastify-js.d.ts │ │ └── vendor/ │ │ └── shims/ │ │ ├── libsodium-wrappers.js │ │ ├── libsodium.js │ │ ├── localforage.js │ │ └── qrcode.js │ ├── libraries/ │ │ ├── questionably-typed/ │ │ │ ├── .eslintignore │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── tsconfig.json │ │ │ └── types/ │ │ │ ├── globals.ts │ │ │ ├── libraries/ │ │ │ │ ├── blakejs.ts │ │ │ │ ├── ember.ts │ │ │ │ └── promise-worker-bi.ts │ │ │ ├── overrides.ts │ │ │ ├── package-augmentations.ts │ │ │ └── tsconfig.json │ │ └── tsconfig.json │ ├── package.json │ ├── pinochle/ │ │ ├── .editorconfig │ │ ├── .ember-cli │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .template-lintrc.js │ │ ├── .watchmanconfig │ │ ├── README.md │ │ ├── app/ │ │ │ ├── app.ts │ │ │ ├── components/ │ │ │ │ ├── back-of-cards.hbs │ │ │ │ ├── back-of-cards.ts │ │ │ │ ├── hand/ │ │ │ │ │ ├── -animation/ │ │ │ │ │ │ ├── card-chart.ts │ │ │ │ │ │ ├── card.ts │ │ │ │ │ │ ├── hand-chart.ts │ │ │ │ │ │ ├── hand.ts │ │ │ │ │ │ └── key-frames.ts │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── host-game/ │ │ │ │ │ ├── -statechart.ts │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── join-game/ │ │ │ │ │ ├── -statechart.ts │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── lazy/ │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── loader/ │ │ │ │ │ ├── ellipsis.hbs │ │ │ │ │ └── indeterminate.hbs │ │ │ │ ├── name-entry/ │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── options.hbs │ │ │ │ ├── options.ts │ │ │ │ ├── play/ │ │ │ │ │ └── as-guest/ │ │ │ │ │ ├── -statechart.ts │ │ │ │ │ ├── index.hbs │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── overlay-info.hbs │ │ │ │ │ └── players-offline.hbs │ │ │ │ ├── player-list.hbs │ │ │ │ ├── player-order/ │ │ │ │ │ ├── index.hbs │ │ │ │ │ └── index.ts │ │ │ │ ├── playing-card/ │ │ │ │ │ ├── corner-value.hbs │ │ │ │ │ ├── face-value.hbs │ │ │ │ │ └── index.hbs │ │ │ │ ├── share-link.hbs │ │ │ │ └── share-link.ts │ │ │ ├── config/ │ │ │ │ └── environment.d.ts │ │ │ ├── game/ │ │ │ │ ├── card.ts │ │ │ │ ├── deck.ts │ │ │ │ ├── meld.ts │ │ │ │ ├── networking/ │ │ │ │ │ ├── -requirements.ts │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── guest/ │ │ │ │ │ │ ├── display-info.ts │ │ │ │ │ │ ├── game-round.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── guest.ts │ │ │ │ │ ├── host/ │ │ │ │ │ │ ├── game-round.ts │ │ │ │ │ │ ├── game-state.ts │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── host.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── trick.ts │ │ │ │ └── utils/ │ │ │ │ └── move-validation.ts │ │ │ ├── helpers/ │ │ │ │ ├── and.ts │ │ │ │ ├── contains.ts │ │ │ │ ├── english-list.ts │ │ │ │ ├── eq.ts │ │ │ │ ├── is-number.ts │ │ │ │ ├── not.ts │ │ │ │ ├── or.ts │ │ │ │ └── suit-to-symbol.ts │ │ │ ├── index.html │ │ │ ├── modifiers/ │ │ │ │ ├── fit-text.ts │ │ │ │ ├── resize.ts │ │ │ │ └── stack.ts │ │ │ ├── router.ts │ │ │ ├── routes/ │ │ │ │ ├── game.ts │ │ │ │ └── join.ts │ │ │ ├── services/ │ │ │ │ ├── game-manager.ts │ │ │ │ ├── guest/ │ │ │ │ │ ├── dispatcher.ts │ │ │ │ │ └── handler.ts │ │ │ │ └── player-info.ts │ │ │ ├── styles/ │ │ │ │ ├── -variables.css │ │ │ │ ├── app.css │ │ │ │ ├── game.css │ │ │ │ ├── loading.css │ │ │ │ ├── page/ │ │ │ │ │ └── ask-game-type.css │ │ │ │ ├── playing-card.css │ │ │ │ ├── resets.css │ │ │ │ ├── synthwave.css │ │ │ │ ├── transitions.css │ │ │ │ ├── utils.css │ │ │ │ └── z-indexes.css │ │ │ ├── templates/ │ │ │ │ ├── application.hbs │ │ │ │ ├── game-full.hbs │ │ │ │ ├── game.hbs │ │ │ │ ├── host.hbs │ │ │ │ ├── index.hbs │ │ │ │ ├── join.hbs │ │ │ │ └── not-recognized.hbs │ │ │ ├── tsconfig.json │ │ │ └── utils/ │ │ │ ├── array.ts │ │ │ ├── container.ts │ │ │ ├── dom.ts │ │ │ ├── trig.ts │ │ │ └── use-machine.ts │ │ ├── config/ │ │ │ ├── ember-cli-update.json │ │ │ ├── environment.js │ │ │ ├── netlify/ │ │ │ │ └── _redirects │ │ │ ├── optional-features.json │ │ │ └── targets.js │ │ ├── ember-cli-build.js │ │ ├── package.json │ │ ├── public/ │ │ │ └── robots.txt │ │ ├── testem.js │ │ ├── tests/ │ │ │ ├── -pages/ │ │ │ │ └── join.ts │ │ │ ├── acceptance/ │ │ │ │ ├── game-test.ts │ │ │ │ └── join-test.ts │ │ │ ├── helpers/ │ │ │ │ ├── .gitkeep │ │ │ │ └── index.ts │ │ │ ├── index.html │ │ │ ├── integration/ │ │ │ │ ├── components/ │ │ │ │ │ ├── host-game-test.ts │ │ │ │ │ ├── lazy-test.ts │ │ │ │ │ ├── playing-card-test.ts │ │ │ │ │ └── share-link-test.js │ │ │ │ └── modifiers/ │ │ │ │ ├── fit-text-test.js │ │ │ │ ├── resize-test.js │ │ │ │ └── stack-test.js │ │ │ ├── test-helper.ts │ │ │ ├── tsconfig.json │ │ │ ├── type-support.ts │ │ │ └── unit/ │ │ │ ├── game/ │ │ │ │ ├── deck-test.ts │ │ │ │ ├── meld-test.ts │ │ │ │ ├── state-test.ts │ │ │ │ ├── trick-test.ts │ │ │ │ └── utils/ │ │ │ │ ├── availableMoves-test.ts │ │ │ │ └── isValidMove-test.ts │ │ │ └── helpers/ │ │ │ ├── contains-test.ts │ │ │ ├── eq-test.ts │ │ │ ├── is-number-test.ts │ │ │ └── suit-to-symbol-test.ts │ │ ├── translations/ │ │ │ └── en-us.yaml │ │ ├── tsconfig.compiler-options.json │ │ ├── tsconfig.json │ │ ├── types/ │ │ │ ├── libraries.d.ts │ │ │ └── overrides.d.ts │ │ └── vendor/ │ │ └── .gitkeep │ ├── scripts/ │ │ └── clean.sh │ ├── smoke-tests/ │ │ ├── .eslintrc.js │ │ ├── .faltestrc.js │ │ ├── .prettierrc.js │ │ ├── helpers/ │ │ │ └── start-server.js │ │ ├── package.json │ │ ├── page-objects/ │ │ │ ├── add-friend.js │ │ │ ├── chat.js │ │ │ └── login.js │ │ └── tests/ │ │ └── smoke-test.js │ ├── stylelint.config.js │ └── tsconfig.json ├── crowdin.yml ├── images/ │ └── icon.xcf └── scripts/ ├── deploy ├── docker-compose.yml ├── dockerhub ├── install-chrome-apt └── publish ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codeclimate.yml ================================================ version: 2 plugins: duplication: enabled: true config: languages: javascript: mass_threshold: 50 # TODO: errors fixme: enabled: false exclude_patterns: - '**/english.ts' - '**/benchmarks/' - '**/ember-cli-build.js' - 'benchmarks/' - 'client/android-wrapper/' - 'relays/' - 'client/web/emberclear/config/' - 'client/web/emberclear/tests/' - 'client/web/emberclear/vendor/' - 'client/web/emberclear/dist/' - 'client/web/emberclear/types/' - '**/node_modules/' - '**/dist/' - '**/tests/' - '**/config/' - '**/script/' - '**/vendor/' - '**/*.d.ts' ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: .github/renovate.json5 ================================================ // Docs: // https://docs.renovatebot.com/configuration-options/ { "extends": [ "config:base", "github>NullVoxPopuli/renovate:weekly.json5", "github>NullVoxPopuli/renovate:npm-groups.json5" ], "automerge": false, "masterIssue": true, "ignorePaths": [ // Deployed once and forgotten "client/android-wrapper" ], "packageRules": [ { // These are one-time experiements and don't need to be kept up to date "paths": ["benchmarks/**"], "enabled": false }, { // libraries and addons aka "non-apps" "paths": ["client/web/addons/**/package.json", "client/web/libraries/**/package.json"], "rangeStrategy": "bump", "schedule": ["after 9pm on sunday"] }, { "paths": ["client/web/emberclear/package.json", "client/web/pinochle/package.json"], // Pin requires more package.json/lockfile churn, // but it allows us to determine if an otherwise floating dep // introduced a regression "rangeStrategy": "pin", // then setting a schedule reduces the churn and allows for PRs to be combined with the groups below "schedule": ["after 9pm on sunday"] }, { "paths": ["client/web/emberclear/Dockerfile"], "enabled": false }, //////////////////////////////////////// // Grouping namespaced packages together //////////////////////////////////////// { "packagePatterns": ["^@babel*"], "schedule": ["after 9pm on sunday"], "groupName": "Babel Transpilation" }, { "packagePatterns": ["^@ember-data*"], "schedule": ["after 9pm on sunday"], "groupName": "Ember Data" }, { "packagePatterns": ["^@faltest*"], "schedule": ["after 9pm on sunday"], "groupName": "FalTest Smoke Testing by CrowdStrike" }, { "packagePatterns": ["^@types\/*"], "schedule": ["after 9pm on sunday"], "groupName": "Type Definitions" }, { "packagePatterns": ["^@embroider*"], "schedule": ["after 9pm on sunday"], "groupName": "embroider" }, { "groupName": "Lint Dependencies", "schedule": ["after 9pm on sunday"], "packageNames": [ "eslint", "babel-eslint", "ember-template-lint", "prettier" ], "packagePatterns": [ "eslint-plugin-.*", "eslint-config-.*", ".*typescript-eslint.*", "^@commitlint\/*", "^remark-*" ], }, // These are dependencies that come default when // generating a new ember addon { "groupName": "Framework Dependencies", "packageNames": [ "@ember/optional-features", "@glimmer/component", "@glimmer/tracking", "ember-disable-prototype-extensions", "ember-export-application-global", "ember-load-initializers", "ember-maybe-import-regenerator", "ember-resolver", "ember-source", "ember-cli-page-title" ] }, { "groupName": "CLI Dependencies", "schedule": ["after 9pm on sunday"], "packageNames": [ "broccoli-asset-rev", "ember-cli", "ember-auto-import", "ember-cli-dependency-checker", "ember-cli-inject-live-reload", "ember-cli-sri", "ember-cli-terser", "ember-cli-htmlbars" ] }, { "groupName": "Testing Dependencies", "schedule": ["after 9pm on sunday"], "packageNames": [ "qunit-dom", "ember-try", "ember-source-channel-url", "ember-qunit", "qunit", "npm-run-all", "@xstate/test", "ember-cli-page-object", "fractal-page-object" ] } ] } ================================================ FILE: .github/workflows/web-addon-crypto.yml ================================================ name: '@emberclear/crypto' # Inspiration: # https://github.com/alexdiliberto/ember-transformicons/blob/master/.github/workflows/ci.yml on: pull_request: push: # filtering branches here prevents duplicate builds from pull_request and push branches: - master paths: - 'client/web/addons/crypto/' env: CI: true root: client/web/ addon: addons/crypto/ full: client/web/addons/crypto/ jobs: lint: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: 'Lint' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.full }} run: yarn eslint . --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build # - uses: wagoid/commitlint-github-action@v1 # env: # GITHUB_TOKEN: ${{ secrets.GH_PAT }} tests_ember_compat: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: Ember Compatability runs-on: ubuntu-latest timeout-minutes: 5 strategy: matrix: scenario: # - "ember-lts-3.16" - "ember-lts-3.20" # - "ember-release" # - "ember-beta" # - "ember-canary" steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install run: yarn install working-directory: ${{ env.root }} - name: "Test: ${{ matrix.scenario }}" working-directory: ${{ env.full }} run: yarn test:try-one ${{ matrix.scenario }} ================================================ FILE: .github/workflows/web-addon-encoding.yml ================================================ name: '@emberclear/encoding' # Inspiration: # https://github.com/alexdiliberto/ember-transformicons/blob/master/.github/workflows/ci.yml on: pull_request: push: # filtering branches here prevents duplicate builds from pull_request and push branches: - master paths: - 'client/web/addons/encoding/' env: CI: true root: client/web/ addon: addons/encoding/ full: client/web/addons/encoding/ jobs: lint: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: 'Lint' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.full }} run: yarn eslint . --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build # - uses: wagoid/commitlint-github-action@v1 # env: # GITHUB_TOKEN: ${{ secrets.GH_PAT }} tests_ember_compat: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: Ember Compatability runs-on: ubuntu-latest timeout-minutes: 5 strategy: matrix: scenario: # - "ember-lts-3.16" - "ember-lts-3.20" # - "ember-release" # - "ember-beta" # - "ember-canary" steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install run: yarn install working-directory: ${{ env.root }} - name: "Test: ${{ matrix.scenario }}" working-directory: ${{ env.full }} run: yarn test:try-one ${{ matrix.scenario }} ================================================ FILE: .github/workflows/web-addon-local-account.yml ================================================ name: '@emberclear/local-account' # Inspiration: # https://github.com/alexdiliberto/ember-transformicons/blob/master/.github/workflows/ci.yml on: pull_request: push: # filtering branches here prevents duplicate builds from pull_request and push branches: - master paths: - 'client/web/addons/local-account/' env: CI: true root: client/web/ addon: addons/local-account/ full: client/web/addons/local-account/ jobs: lint: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: 'Lint' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.full }} run: yarn eslint . --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build # - uses: wagoid/commitlint-github-action@v1 # env: # GITHUB_TOKEN: ${{ secrets.GH_PAT }} tests_ember_compat: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: Ember Compatability runs-on: ubuntu-latest timeout-minutes: 5 strategy: matrix: scenario: # - "ember-lts-3.16" - "ember-lts-3.20" # - "ember-release" # - "ember-beta" # - "ember-canary" steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install run: yarn install working-directory: ${{ env.root }} - name: "Test: ${{ matrix.scenario }}" working-directory: ${{ env.full }} run: yarn test:try-one ${{ matrix.scenario }} ================================================ FILE: .github/workflows/web-addon-networking.yml ================================================ name: '@emberclear/networking' # Inspiration: # https://github.com/alexdiliberto/ember-transformicons/blob/master/.github/workflows/ci.yml on: pull_request: push: # filtering branches here prevents duplicate builds from pull_request and push branches: - master paths: - 'client/web/addons/networking/' env: CI: true root: client/web/ addon: addons/networking/ full: client/web/addons/networking/ jobs: lint: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: 'Lint' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.full }} run: yarn eslint . --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build # - uses: wagoid/commitlint-github-action@v1 # env: # GITHUB_TOKEN: ${{ secrets.GH_PAT }} tests_ember_compat: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: Ember Compatability runs-on: ubuntu-latest timeout-minutes: 5 strategy: matrix: scenario: # - "ember-lts-3.16" - "ember-lts-3.20" # - "ember-release" # - "ember-beta" # - "ember-canary" steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install run: yarn install working-directory: ${{ env.root }} - name: "Test: ${{ matrix.scenario }}" working-directory: ${{ env.full }} run: yarn test:try-one ${{ matrix.scenario }} ================================================ FILE: .github/workflows/web-addon-test-helpers.yml ================================================ name: '@emberclear/test-helpers' # Inspiration: # https://github.com/alexdiliberto/ember-transformicons/blob/master/.github/workflows/ci.yml on: pull_request: push: # filtering branches here prevents duplicate builds from pull_request and push branches: - master paths: - 'client/web/addons/test-helpers/' env: CI: true root: client/web/ addon: addons/test-helpers/ full: client/web/addons/test-helpers/ jobs: lint: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: 'Lint' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.full }} run: yarn eslint . --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build # - uses: wagoid/commitlint-github-action@v1 # env: # GITHUB_TOKEN: ${{ secrets.GH_PAT }} tests_ember_compat: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: Ember Compatability runs-on: ubuntu-latest timeout-minutes: 5 strategy: matrix: scenario: # - "ember-lts-3.16" - "ember-lts-3.20" # - "ember-release" # - "ember-beta" # - "ember-canary" steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install run: yarn install working-directory: ${{ env.root }} - name: "Test: ${{ matrix.scenario }}" working-directory: ${{ env.full }} run: yarn test:try-one ${{ matrix.scenario }} ================================================ FILE: .github/workflows/web-addon-tracked-local-storage.yml ================================================ name: 'ember-tracked-local-storage' # Inspiration: # https://github.com/alexdiliberto/ember-transformicons/blob/master/.github/workflows/ci.yml on: pull_request: push: # filtering branches here prevents duplicate builds from pull_request and push branches: - master paths: - 'client/web/addons/tracked-local-storage/' env: CI: true root: client/web/ addon: addons/tracked-local-storage/ full: client/web/addons/tracked-local-storage/ jobs: lint: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: 'Lint' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.full }} run: yarn eslint . --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build # - uses: wagoid/commitlint-github-action@v1 # env: # GITHUB_TOKEN: ${{ secrets.GH_PAT }} tests_ember_compat: if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" name: Ember Compatability runs-on: ubuntu-latest timeout-minutes: 5 strategy: matrix: scenario: - "ember-lts-3.16" - "ember-lts-3.20" - "ember-release" - "ember-beta" - "ember-canary" steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install run: yarn install working-directory: ${{ env.root }} - name: "Test: ${{ matrix.scenario }}" working-directory: ${{ env.full }} run: yarn test:try-one ${{ matrix.scenario }} # TODO: figure out if this supports working-directory # publish: # name: Release # runs-on: ubuntu-latest # if: github.ref == 'refs/heads/master' # needs: [tests_ember_compat, lint] # steps: # - uses: actions/checkout@v1 # - uses: volta-cli/action@v1 # - run: yarn install # working-directory: ${{ env.root }} # - name: Release # working-directory: ${{ env.root }} # env: # GITHUB_TOKEN: ${{ secrets.GH_PAT }} # NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # run: yarn semantic-release ================================================ FILE: .github/workflows/web-addon-ui.yml ================================================ name: 'Web @emberclear/ui' on: pull_request: branches: [master] paths: - 'client/web/addons/ui/**' - 'client/web/package.json' env: root: client/web/ addon: addons/ui/ full: client/web/addons/ui/ jobs: lint: name: 'Lint' runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.root }} run: yarn eslint ${{ env.addon }} --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: "Styles" working-directory: ${{ env.root }} run: yarn stylelint ${{ env.addon }}**/*.css - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build test: name: 'Test' runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: Ember working-directory: "${{ env.root }}/${{ env.addon }}" run: yarn test ================================================ FILE: .github/workflows/web-app-deploy.yml ================================================ name: Web App Deploy on: push: branches: [master] paths: - 'client/web/**' env: cwd: client/web/emberclear jobs: tests: name: Tests runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.cwd }} run: yarn install - name: Test working-directory: ${{ env.cwd }} env: PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} # run: ./scripts/test-with-coverage.sh run: yarn ember test # Coverage Disabled while these are worked out: # https://github.com/babel/ember-cli-babel/issues/350 # https://github.com/kategengler/ember-cli-code-coverage/issues/265 # - name: Upload Coverage to Coveralls # uses: coverallsapp/github-action@v1.0.1 # with: # github-token: ${{ secrets.github_token }} # path-to-lcov: ./client/web/emberclear/coverage/lcov.info # - name: Upload Coverage Artifacts # uses: actions/upload-artifact@v1 # with: # name: coverage # path: client/web/emberclear/coverage/ deploy: name: Deploy to Netlify runs-on: ubuntu-latest timeout-minutes: 15 needs: - tests steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 # - name: Download Coverage Artifacts # uses: actions/download-artifact@v1 # with: # name: coverage # path: client/web/emberclear/coverage/ - name: Install working-directory: ${{ env.cwd }} run: yarn install - run: yarn global add netlify-cli - name: Deploy to Netlify env: FRONTEND: client/web/emberclear NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} run: | COVERAGE_DIR="client/web/emberclear/coverage" if [ -d "$COVERAGE_DIR" ]; then mv $COVERAGE_DIR client/web/emberclear/public/ fi ( cd client/web/emberclear \ && time yarn analyze \ && time yarn build:production ) time ./scripts/publish - name: Upload Built Asset Artifacts uses: actions/upload-artifact@master with: name: frontend-assets path: client/web/emberclear/dist/ deploy_docker: name: Deploy Docker Image runs-on: ubuntu-latest needs: [tests, deploy] timeout-minutes: 5 steps: - uses: actions/checkout@v2 - name: Download Built Asset Artifacts uses: actions/download-artifact@master with: name: frontend-assets path: client/web/emberclear/dist/ - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@master with: name: nullvoxpopuli/emberclear username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASSWORD }} snapshot: true workdir: client/web/emberclear dockerfile: Dockerfile.release tests_e2e: name: E2E Tests runs-on: ubuntu-latest timeout-minutes: 15 needs: [deploy] steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Test run: | cd client/web/smoke-tests \ && yarn \ && yarn test --headless # Deploy via Script (requires docker environment on VM) # - uses: actions/docker/cli@master # - name: Publish to DockerHub # env: # DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} # DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} # # DOCKER_HOST: tcp://docker:2375/ # # DOCKER_DRIVER: overlay2 # run: sh ./scripts/dockerhub ================================================ FILE: .github/workflows/web-app-quality.yml ================================================ name: Web App Quality on: pull_request: branches: [master] paths: - 'client/web/emberclear/**' - 'client/web/package.json' env: root: client/web/ app: emberclear/ full: client/web/emberclear/ jobs: security: name: Dependency Security runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install run: volta install node && volta install snyk - name: Snyk working-directory: ${{ env.root }} if: github.event == 'pull_request' env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} run: snyk test --severity-threshold=high lint: name: "Lint JS/TS & Type Checking" runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.root }} run: yarn eslint ${{ env.app }} --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: "Styles" working-directory: ${{ env.root }} run: yarn stylelint ${{ env.app }}**/*.css - name: "Translations" working-directory: ${{ env.full }} run: yarn lint:i18n continue-on-error: true - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build ================================================ FILE: .github/workflows/web-app-tests.yml ================================================ name: Web App Tests on: pull_request: branches: [master] paths: - 'client/web/**' env: cwd: client/web/emberclear name: emberclear ############################################################## jobs: tests: name: Tests strategy: matrix: # os: [ubuntu-latest, macOS-latest, windows-latest] # browsers: [chrome, firefox, safari, edge] ci_browser: - Chrome # Firefox is flaky in Github........ # - Firefox runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - uses: actions/cache@v2 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - name: Install working-directory: ${{ env.cwd }} run: yarn install - name: Test working-directory: ${{ env.cwd }} env: PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} CI_BROWSER: ${{ matrix.ci_browser }} # run: ./scripts/test-with-coverage.sh run: yarn ember test # Coverage Disabled while these are worked out: # https://github.com/babel/ember-cli-babel/issues/350 # https://github.com/kategengler/ember-cli-code-coverage/issues/265 # # - name: Upload Coverage to Coveralls # uses: coverallsapp/github-action@v1.0.1 # with: # github-token: ${{ secrets.github_token }} # path-to-lcov: ./client/web/emberclear/coverage/lcov.info # - name: Upload Coverage Artifacts # uses: actions/upload-artifact@v1 # with: # name: coverage # path: client/web/emberclear/coverage/ ############################################################## # bundle_analysis: name: Bundle Analysis runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - uses: actions/cache@v2 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - name: Install working-directory: ${{ env.cwd }} run: yarn install - name: Analyze Bundle working-directory: ${{ env.cwd }} run: yarn analyze - name: Upload Bundle Analysis Artifacts uses: actions/upload-artifact@v2 with: name: built_bundle_analysis path: client/web/emberclear/public/bundle build_app: name: Build App runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - uses: actions/cache@v2 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - name: Install and Build run: | ( cd client/web/emberclear && yarn install && yarn build:production ) cp ${{ env.cwd }}/config/netlify/_redirects ${{ env.cwd }}/dist - name: Upload App Artifacts uses: actions/upload-artifact@v2 with: name: built_app path: client/web/emberclear/dist/ ############################################################## tests_e2e: name: E2E Tests runs-on: ubuntu-latest timeout-minutes: 15 needs: [deploy_preview] steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Get Deploy URL uses: actions/download-artifact@master with: name: deploy-url path: ./ - name: Test run: | export DEPLOY_URL=$(cat ./deploy-url.txt) cd client/web/smoke-tests DETECT_CHROMEDRIVER_VERSION=true yarn yarn test --target pull-request --headless ############################################################## deploy_preview: name: Deploy Preview runs-on: ubuntu-latest timeout-minutes: 15 needs: [bundle_analysis, build_app] steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Download Built Bundle Analysis Artifacts uses: actions/download-artifact@master with: name: built_bundle_analysis path: ./deploy/bundle-analysis/ - name: Download Built App Artifacts uses: actions/download-artifact@master with: name: built_app path: ./deploy/app/ - name: Combine Bundle Analysis with App run: | mkdir -p ./deploy/dist/bundle/ mv ./deploy/app/* ./deploy/dist/ cp ./deploy/bundle-analysis/* ./deploy/dist/bundle/ cp ${{ env.cwd }}/config/netlify/_redirects ./deploy/dist/ - name: Deploy to Netlify id: deploy uses: nwtgck/actions-netlify@v1.2.2 with: publish-dir: './deploy/dist' production-branch: __handled_separately__ github-token: ${{ secrets.GITHUB_TOKEN }} env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} - run: echo "${{ steps.deploy.outputs.deploy-url }}" > deploy-url.txt - name: Upload URL as Artifact uses: actions/upload-artifact@v2 with: name: deploy-url path: deploy-url.txt ############################################################## lhci: name: Lighthouse CI runs-on: ubuntu-latest needs: [build_app] steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Download Built App Artifacts uses: actions/download-artifact@master with: name: built_app path: client/web/emberclear/dist/ - name: run Lighthouse env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} run: | volta install node volta install @lhci/cli@0.3.x cd client/web/emberclear lhci collect \ --upload.target=temporary-public-storage \ --staticDistDir=./dist \ --githubToken $GITHUB_TOKEN \ --githubAppToken $LHCI_GITHUB_APP_TOKEN ================================================ FILE: .github/workflows/web-pinochle-deploy.yml ================================================ name: Web Pinochle Deploy on: push: branches: [master] paths: - 'client/web/pinochle/**' - '.github/workflows/web-pinochle-*' env: cwd: client/web/pinochle jobs: tests: name: Tests runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.cwd }} run: yarn install - name: Test working-directory: ${{ env.cwd }} # run: ./scripts/test-with-coverage.sh run: yarn ember test # Coverage Disabled while these are worked out: # https://github.com/babel/ember-cli-babel/issues/350 # https://github.com/kategengler/ember-cli-code-coverage/issues/265 # - name: Upload Coverage to Coveralls # uses: coverallsapp/github-action@v1.0.1 # with: # github-token: ${{ secrets.github_token }} # path-to-lcov: ./client/web/emberclear/coverage/lcov.info # - name: Upload Coverage Artifacts # uses: actions/upload-artifact@v1 # with: # name: coverage # path: client/web/emberclear/coverage/ deploy: name: Deploy to Netlify runs-on: ubuntu-latest timeout-minutes: 15 needs: - tests steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 # - name: Download Coverage Artifacts # uses: actions/download-artifact@v1 # with: # name: coverage # path: client/web/emberclear/coverage/ - name: Install working-directory: ${{ env.cwd }} run: yarn install - run: yarn global add netlify-cli - name: Deploy to Netlify env: FRONTEND: ${{ env.cwd }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PINOCHLE }} NETLIFY_CLI_VERSION: 0.4.0 run: | COVERAGE_DIR="${{ env.cwd }}/coverage" if [ -d "$COVERAGE_DIR" ]; then mv $COVERAGE_DIR ${{ env.cwd }}/public/ fi ( cd ${{ env.cwd }} \ && time yarn build:production ) time ./scripts/publish - name: Upload Built Asset Artifacts uses: actions/upload-artifact@master with: name: frontend-assets path: ${{ env.cwd }}/dist/ # tests_e2e: # name: E2E Tests # runs-on: ubuntu-latest # timeout-minutes: 15 # needs: # - deploy # steps: # - name: 'Wait for status checks' # id: waitforstatuschecks # uses: "wyrihaximus/github-action-wait-for-status@v2" # with: # ignoreActions: tests_e2e,"E2E Tests" # checkInterval: 30 # - uses: actions/checkout@v2 # - uses: volta-cli/action@v1 # - name: Test # run: | # cd client/web/smoke-tests \ # && yarn \ # && yarn test --headless # Deploy via Script (requires docker environment on VM) # - uses: actions/docker/cli@master # - name: Publish to DockerHub # env: # DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} # DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} # # DOCKER_HOST: tcp://docker:2375/ # # DOCKER_DRIVER: overlay2 # run: sh ./scripts/dockerhub ================================================ FILE: .github/workflows/web-pinochle-quality.yml ================================================ name: Web Pinochle Quality on: pull_request: branches: [master] paths: - 'client/web/pinochle/**' - 'client/web/package.json' env: root: client/web/ app: pinochle/ full: client/web/pinochle/ jobs: security: name: Dependency Security runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install run: volta install node && volta install snyk - name: Snyk working-directory: ${{ env.root }} if: github.event == 'pull_request' env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} run: snyk test --severity-threshold=high lint: name: "Lint JS/TS & Type Checking" runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.root }} run: yarn eslint ${{ env.app }} --ext js,ts - name: "Templates" working-directory: ${{ env.full }} run: yarn ember-template-lint . - name: "Styles" working-directory: ${{ env.root }} run: yarn stylelint ${{ env.app }}**/*.css - name: 'Type Correctness' working-directory: ${{ env.full }} run: yarn tsc --build ================================================ FILE: .github/workflows/web-pinochle-tests.yml ================================================ name: Web Pinochle Tests on: pull_request: branches: [master] paths: - 'client/web/pinochle/**' env: cwd: client/web/pinochle name: pinochle ############################################################## jobs: tests: name: Tests strategy: matrix: # os: [ubuntu-latest, macOS-latest, windows-latest] # browsers: [chrome, firefox, safari, edge] ci_browser: - Chrome - Firefox # - Safari runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - uses: actions/cache@v2 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - name: Install working-directory: ${{ env.cwd }} run: yarn install - name: Test working-directory: ${{ env.cwd }} env: CI_BROWSER: ${{ matrix.ci_browser }} # run: ./scripts/test-with-coverage.sh run: yarn ember test # Coverage Disabled while these are worked out: # https://github.com/babel/ember-cli-babel/issues/350 # https://github.com/kategengler/ember-cli-code-coverage/issues/265 # # - name: Upload Coverage to Coveralls # uses: coverallsapp/github-action@v1.0.1 # with: # github-token: ${{ secrets.github_token }} # path-to-lcov: ./client/web/emberclear/coverage/lcov.info # - name: Upload Coverage Artifacts # uses: actions/upload-artifact@v1 # with: # name: coverage # path: client/web/emberclear/coverage/ ############################################################## # # bundle_analysis: # name: Bundle Analysis # runs-on: ubuntu-latest # timeout-minutes: 15 # steps: # - uses: actions/checkout@v2 # - uses: volta-cli/action@v1 # - uses: actions/cache@v2 # with: # path: '**/node_modules' # key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} # - name: Install # working-directory: ${{ env.cwd }} # run: yarn install # - name: Analyze Bundle # working-directory: ${{ env.cwd }} # run: yarn analyze # - name: Upload Bundle Analysis Artifacts # uses: actions/upload-artifact@v2 # with: # name: built_bundle_analysis # path: client/web/emberclear/public/bundle build_app: name: Build App runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - uses: actions/cache@v2 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - name: Install and Build run: | ( cd ${{ env.cwd }} && yarn install && yarn build:production ) cp ${{ env.cwd }}/config/netlify/_redirects ${{ env.cwd }}/dist - name: Upload App Artifacts uses: actions/upload-artifact@v2 with: name: built_${{ env.name }} path: ${{ env.cwd }}/dist/ ############################################################## # TODO: scope e2e tests to app # tests_e2e: # name: E2E Tests # runs-on: ubuntu-latest # timeout-minutes: 15 # needs: [deploy_preview] # steps: # - uses: actions/checkout@v2 # - uses: volta-cli/action@v1 # - name: Get Deploy URL # uses: actions/download-artifact@master # with: # name: deploy-url # path: ./ # - name: Test # run: | # export DEPLOY_URL=$(cat ./deploy-url.txt) # cd client/web/smoke-tests # DETECT_CHROMEDRIVER_VERSION=true yarn # yarn test --target pull-request --headless ############################################################## deploy_preview: name: Deploy Preview runs-on: ubuntu-latest timeout-minutes: 15 needs: # - bundle_analysis - build_app steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 # - name: Download Built Bundle Analysis Artifacts # uses: actions/download-artifact@master # with: # name: built_bundle_analysis # path: ./deploy/bundle-analysis/ - name: Download Built App Artifacts uses: actions/download-artifact@master with: name: built_${{ env.name }} path: ./deploy/app/ - name: Combine Bundle Analysis with App run: | mkdir -p ./deploy/dist/bundle/ mv ./deploy/app/* ./deploy/dist/ cp ${{ env.cwd }}/config/netlify/_redirects ./deploy/dist/ # cp ./deploy/bundle-analysis/* ./deploy/dist/bundle/ - name: Deploy to Netlify id: deploy uses: nwtgck/actions-netlify@v1.2.2 with: publish-dir: './deploy/dist' production-branch: __handled_separately__ github-token: ${{ secrets.GITHUB_TOKEN }} env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PINOCHLE }} - run: echo "${{ steps.deploy.outputs.deploy-url }}" > deploy-url.txt - name: Upload URL as Artifact uses: actions/upload-artifact@v2 with: name: deploy-url path: deploy-url.txt ############################################################## # lhci: # name: Lighthouse CI # runs-on: ubuntu-latest # needs: [build_app] # steps: # - uses: actions/checkout@v2 # - uses: volta-cli/action@v1 # - name: Download Built App Artifacts # uses: actions/download-artifact@master # with: # name: built_app # path: ${{ env.cwd }}/dist/ # - name: run Lighthouse # env: # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} # run: | # volta install node # volta install @lhci/cli@0.3.x # cd ${{ env.cwd }} # lhci collect \ # --upload.target=temporary-public-storage \ # --staticDistDir=./dist \ # --githubToken $GITHUB_TOKEN \ # --githubAppToken $LHCI_GITHUB_APP_TOKEN ================================================ FILE: .github/workflows/web-smoke-tests.yml ================================================ name: Web Smoke Tests on: pull_request: branches: [master] paths: - 'client/web/smoke-tests/**' - 'client/web/package.json' env: root: client/web/ dir: smoke-tests/ full: client/web/addons/ui/ ############################################################## jobs: lint: name: 'Lint' runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Install working-directory: ${{ env.root }} run: yarn install - name: "JS/TS" working-directory: ${{ env.root }} run: yarn eslint ${{ env.dir }} --ext js,ts # No TypeScript (yet) # - name: 'Type Correctness' # working-directory: ${{ env.full }} # run: yarn tsc --skipLibCheck --noEmit ############################################################## tests: name: E2E Tests runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - name: Test Production run: | cd client/web/smoke-tests DETECT_CHROMEDRIVER_VERSION=true yarn yarn test --headless ================================================ FILE: .gitignore ================================================ node_modules declarations/ tmp dist _build deps *.log .idea/ .DS_Store client/keystore.jks client/upload_certificate.pem client/Google Play Store Info # Local Netlify folder .netlify # Local lighthouse ci .lighthouseci ================================================ FILE: .gitmodules ================================================ [submodule "relays/phoenix-relay"] path = relays/phoenix-relay url = https://github.com/NullVoxPopuli/mesh-relay-phoenix.git ================================================ FILE: .sonarcloud.properties ================================================ sonar.exclusions=**/*-test.{ts,js} ================================================ 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": "node", "request": "launch", "name": "emberclear UI", "skipFiles": ["/**"], "cwd": "${workspaceFolder}/client/web/emberclear/", "runtimeExecutable": "yarn", "runtimeArgs": ["start"], }, { "type": "node", "request": "launch", "name": "Frontend E2E Tests", "skipFiles": ["/**"], "cwd": "${workspaceFolder}/client/web/smoke-tests/", "program": "${workspaceFolder}/client/web/smoke-tests/node_modules/@faltest/cli/bin/index.js", "args": [ "--browsers", "2", "--timeouts-override", "900000", "--target", "ember", // "local", ] } ] } ================================================ FILE: .whitesource ================================================ { "checkRunSettings": { "vulnerableCheckRunConclusionLevel": "failure" }, "issueSettings": { "minSeverityLevel": "LOW" } } ================================================ FILE: CHANGELOG.md ================================================ ## Change Log ### 2019-01 - Bugfix: configurable relays did not correctly implement relay selection during app boot ### 2018-12 - Enhancement: Split out the settings screen for future expandability. - Enhancement: Allow the relays to be configured. - Enhancement: Ensure that the default relays always exist as configuration options. - Bugfix: Sometimes the toast messages would not have a background color, making them very hard to read. - Chore: Reorganize some toplevel components to be a part of the application route's private collection. - Enhancement: for browsers that are not compatible, show a compatibility message. - Bugfix: Regression where notification prompt would not hide - Enhancement: Can mark a message for automatic resend. When the recipient isn't online, a message cannot be sent to them. With a message marked for automatic resend, the next time the recipient comes online, the message will be immediately sent, without the need to interact with the Chat. - Chore: Change Log Created. Tagged releases will be monthly. - See git log for prior enhancements, bugfixes, etc ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## Building emberclear can be built and run with ``` cd client/web/emberclear yarn install yarn start:dev ``` and then can be visited at `https://localhost:4201/`. ## Testing Run the tests locally with ``` cd client/web/emberclear yarn test ``` ## For working with the Relay ```bash git submodule update --init --recursive cd client/web/emberclear && yarn start:dev ``` #### Debugging Module Resolution: ```js // shows all detected services Object.keys(window.requirejs.entries).filter(b => b.includes("service")) ``` File Watch Problems? ```bash echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p ``` ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License” refers to version 3 of the GNU General Public License. “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. A “covered work” means either the unmodified Program or a work based on the Program. To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # [emberclear](https://emberclear.io) emberclear is published at: https://emberclear.io and can be run locally with docker via ``` docker run -d -p 4201:80 nullvoxpopuli/emberclear ``` and then can be visited at `http://localhost:4201`. ## Project Directory - [Browser Client](https://github.com/NullVoxPopuli/emberclear/tree/master/client/web/emberclear) - [Phoenix Relay](https://github.com/NullVoxPopuli/mesh-relay-phoenix) - [Benchmarks](https://github.com/NullVoxPopuli/emberclear/tree/master/benchmarks) ## Another Chat App? Yes, there is a lack of trust that manifests when existing chat apps are closed source and centralized. Emberclear, by design, is trustless -- meaning that, while there is a server component, the server knows nothing more than your "_public_ key". The server(s) are also meant to be a hot-swappable member of a mesh network, so no one implementation matters, as long as the same protocol is used. Here is a table of detailing out some differences between emberclear and other chat apps: ## Development See: [CONTRIBUTING.md](https://github.com/NullVoxPopuli/emberclear/blob/master/CONTRIBUTING.md) ## Special Thanks - Cross-Browser / Cross-Platform Testing and Automation ## License [GNU General Public License version 3](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)#summary) ================================================ FILE: benchmarks/crypto/.babelrc.js ================================================ module.exports = { presets: [ ['@babel/env', { targets: { node: '10' } }], '@babel/preset-typescript', ], plugins: [ '@babel/plugin-transform-modules-commonjs', '@babel/plugin-syntax-dynamic-import', '@babel/plugin-proposal-class-properties', ['@babel/plugin-proposal-decorators', { legacy: true }], '@babel/plugin-transform-runtime', ] }; ================================================ FILE: benchmarks/crypto/.gitignore ================================================ /build ================================================ FILE: benchmarks/crypto/package.json ================================================ { "name": "crypto-benchmarks", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "build": "babel ./src --out-dir ./build --extensions '.ts'", "start": "node build/index.js", "bench": "yarn build && yarn start", "debug": "node --inspect-brk=9229 build/index.js" }, "dependencies": { "js-nacl": "^1.3.2", "libsodium-wrappers": "^0.7.3", "tweetnacl": "^1.0.0", "tweetnacl-util": "^0.15.0" }, "devDependencies": { "@babel/cli": "7.6.2", "@babel/core": "7.6.2", "@babel/node": "7.6.2", "@babel/plugin-proposal-class-properties": "7.5.5", "@babel/plugin-proposal-decorators": "7.6.0", "@babel/plugin-syntax-dynamic-import": "7.2.0", "@babel/plugin-transform-modules-commonjs": "7.6.0", "@babel/plugin-transform-runtime": "7.6.2", "@babel/preset-env": "7.6.2", "@babel/preset-typescript": "7.6.0", "@babel/register": "7.6.2", "@babel/runtime": "7.6.2", "@types/js-nacl": "1.2.0", "@types/libsodium-wrappers": "0.7.5", "@types/node": "12.7.12", "asyncmark": "0.3.1", "typescript": "3.6.3" } } ================================================ FILE: benchmarks/crypto/run ================================================ #!/bin/bash yarn yarn build yarn start ================================================ FILE: benchmarks/crypto/src/bench/base64.ts ================================================ import { Suite } from "asyncmark"; import libsodiumWrapper from "libsodium-wrappers"; import * as jsNaCl from "../lib/js-nacl"; import { libsodium, tweetnacl, } from "../lib/utils"; const msg = Uint8Array.from([104, 101, 108, 101, 111]); // hello export const base64 = new Suite({ async before() { await libsodiumWrapper.ready; await jsNaCl.setInstance(); console.log("\nround trip base64 encode + decode"); } }); base64.add({ name: "libsodium", fun: async () => { libsodium.fromBase64(await libsodium.toBase64(msg)); } }); base64.add({ name: "tweetnacl", fun: async () => { tweetnacl.fromBase64(await tweetnacl.toBase64(msg)); } }); ================================================ FILE: benchmarks/crypto/src/bench/hex.ts ================================================ import { Suite } from "asyncmark"; import libsodiumWrapper from "libsodium-wrappers"; import * as jsNaCl from "../lib/js-nacl"; import { libsodium, tweetnacl, jsnacl, } from "../lib/utils"; import { Buffer } from "buffer"; const msg = Uint8Array.from([104, 101, 108, 101, 111]); // hello export const hex = new Suite({ async before() { await libsodiumWrapper.ready; await jsNaCl.setInstance(); console.log("\nround trip uint8 <-> string encode + decode"); } }); hex.add({ name: "libsodium", fun: async () => { libsodium.fromHex(await libsodium.toHex(msg)); } }); // hex.add({ // name: "tweet-nacl", // fun: async () => { // tweetnacl.fromString(await tweetnacl.toString(msg)); // } // }); hex.add({ name: "js-nacl", fun: async () => { jsnacl.fromHex(await jsnacl.toHex(msg)); } }); hex.add({ name: "native ", fun: async() => { const hex = Array.from(msg).map (b => b.toString(16).padStart(2, "0")).join(""); return new Uint8Array(hex.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))); } }); ================================================ FILE: benchmarks/crypto/src/bench/key-generation.ts ================================================ import { Suite } from 'asyncmark'; import * as libsodiumjs from '../lib/libsodium'; import * as tweetNaCl from '../lib/tweet-nacl'; import * as jsNaCl from '../lib/js-nacl'; export const keyGeneration = new Suite({ async before() { console.log('\nKey Generation'); await jsNaCl.setInstance(); } }); keyGeneration.add({ name: 'libsodium', fun: libsodiumjs.generateAsymmetricKeys, }); keyGeneration.add({ name: 'tweetnacl', fun: tweetNaCl.generateAsymmetricKeys, }); keyGeneration.add({ name: 'js-nacl', fun: jsNaCl.generateAsymmetricKeys, }) ================================================ FILE: benchmarks/crypto/src/bench/nonce-generation.ts ================================================ import { Suite } from 'asyncmark'; import * as libsodiumjs from '../lib/libsodium'; import * as tweetNaCl from '../lib/tweet-nacl'; import * as jsNaCl from '../lib/js-nacl'; export const nonceGeneration = new Suite({ async before() { console.log('\nNonce Generation'); await jsNaCl.setInstance(); } }); nonceGeneration.add({ name: 'libsodium', fun: libsodiumjs.generateNonce, }); nonceGeneration.add({ name: 'tweetnacl', fun: tweetNaCl.generateNonce, }); nonceGeneration.add({ name: 'js-nacl', fun: jsNaCl.generateNonce, }); ================================================ FILE: benchmarks/crypto/src/bench/round-trip-long.ts ================================================ import { Suite } from 'asyncmark'; import libsodiumWrapper from 'libsodium-wrappers'; import { fromString } from '../utils'; import * as jsNaCl from '../lib/js-nacl'; import { libsodium, jsnacl, tweetnacl } from '../lib/round-trip-implementations'; const lorem = ` Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse libero risus, porttitor nec urna ut, pellentesque feugiat ex. Integer viverra enim at pulvinar congue. Nunc et turpis vitae nisi maximus laoreet. Mauris malesuada lorem arcu, ut suscipit ante dictum nec. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas eget ex at ligula accumsan tincidunt ac vel erat. Nunc non nunc et dui feugiat finibus. Fusce efficitur, tortor a viverra consequat, sem nulla viverra quam, imperdiet malesuada dui justo vel lacus. Aenean malesuada gravida eros ut dictum. In vehicula vestibulum lacus vel auctor. Proin rutrum ut felis sit amet sagittis. Vivamus quis sapien vel sapien rutrum posuere eu nec nulla. Fusce fermentum nulla et vehicula pharetra. Duis tempor libero cursus, accumsan tellus id, accumsan nulla. Nulla facilisi. In aliquet hendrerit pulvinar. Mauris nulla nibh, vulputate vitae malesuada at, pulvinar id magna. Aliquam eget elit maximus, pretium nunc sit amet, molestie felis. Mauris in dolor imperdiet metus lobortis vestibulum non ac risus. Nunc pretium mattis sapien, a scelerisque libero pharetra nec. Pellentesque nisi est, sollicitudin vitae feugiat ac, fringilla vitae lacus. Donec ullamcorper fringilla dolor, commodo vulputate metus accumsan mollis. Morbi maximus vehicula velit, in congue neque vestibulum sed. Nam volutpat, urna eu posuere consequat, leo metus mollis enim, ut scelerisque lectus leo eu urna. Aliquam porttitor sapien ut risus vehicula imperdiet. Sed nunc nisi, cursus quis porta ac, ultricies non erat. Sed tristique ante at accumsan malesuada. Proin ante urna, lacinia at lacinia quis, condimentum in nunc. Proin ultricies velit nisl, at gravida lectus ultricies in. Aliquam laoreet, purus at commodo feugiat, felis nisl dignissim augue, non dapibus turpis nisi non lacus. In sit amet libero ut risus laoreet tristique eget sit amet erat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Integer varius dolor eu pharetra lobortis. Nam non risus eu lectus congue maximus. Proin mauris justo, condimentum eu rutrum at, lacinia nec urna. Vivamus vitae tristique tortor, non ultrices ante. Aliquam quis tortor et eros dapibus posuere sed non enim. Fusce a dui fringilla, imperdiet neque vel, vestibulum erat. Praesent mi lectus, dignissim posuere vulputate a, tempus et quam. Pellentesque ornare congue neque sit amet rutrum. Ut convallis ac dolor id hendrerit. Duis placerat est sit amet orci egestas congue. Donec sed nunc id leo vestibulum blandit eu eu mauris. Nam eget tempus arcu, ac lobortis metus. Sed dolor nibh, pulvinar sit amet dui at, dictum aliquam quam. Nulla condimentum iaculis arcu. Maecenas vel metus egestas, placerat magna in, mattis massa. Cras et hendrerit purus. Nullam id porta ligula, eget feugiat risus. Nam varius nunc eu elit sodales, congue molestie turpis bibendum. Aenean eu diam dapibus, luctus odio vitae, laoreet ipsum. In vehicula purus id suscipit tristique. Praesent ultrices risus risus, eget imperdiet est rutrum et. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ac ante non turpis interdum sodales. Fusce a ligula eget enim cursus mollis. Maecenas et est magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent consectetur tempus viverra. Sed ultricies molestie blandit. Praesent eu dictum sem. Proin porta elit lacus, vel ornare arcu cursus eu. Integer a dolor ut arcu pellentesque fringilla. Nunc eget suscipit sem, sed pellentesque elit. Nam arcu nisi, condimentum at quam pretium, semper hendrerit massa. Fusce maximus turpis velit, vel molestie est volutpat rhoncus. Praesent finibus lacinia feugiat. Sed in nulla luctus, imperdiet urna et, sodales neque. Mauris commodo mattis sapien id pulvinar. Nulla in nisi eget sem tempus placerat. Nunc at mi sit amet tellus pulvinar imperdiet. Sed consequat efficitur felis, at aliquam est imperdiet quis. In in scelerisque lectus. Suspendisse luctus pretium tortor tincidunt interdum. Nullam ornare arcu vel magna auctor aliquam. Sed ut rutrum nunc. Pellentesque dignissim mattis iaculis. Morbi facilisis interdum neque, eu pulvinar est venenatis at. Fusce lobortis varius justo, in finibus turpis tincidunt ac. Suspendisse et ornare enim, vel placerat mauris. Aliquam laoreet nunc eget ligula convallis, eget aliquet ipsum pulvinar. Praesent tempor nulla non magna dictum lobortis. Praesent eleifend, velit eu semper tristique, tortor velit pharetra metus, nec dignissim mi nibh ut sem. Nulla in finibus ipsum, vitae ullamcorper lorem. Donec ac ligula lacinia, placerat massa ac, mollis odio. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus imperdiet, ante sed pretium vestibulum, orci dui vulputate lacus, vel sodales metus ipsum ut velit. Integer tempus lobortis metus, eu vulputate leo fermentum id. Praesent ut velit ultricies, maximus risus eu, lobortis ante. Nam interdum finibus fermentum. Maecenas in semper purus. Fusce ac enim ac ligula aliquet egestas. In hac habitasse platea dictumst. Morbi purus enim, pellentesque eu sagittis ac, pharetra in leo. Phasellus fermentum felis vitae nisi pulvinar semper. Sed consequat ligula tortor, et posuere orci laoreet vitae. Ut ultrices, urna et volutpat scelerisque, justo neque porta ipsum, ac mollis urna nunc vitae eros. Sed rhoncus nunc et purus finibus, ut pretium libero finibus. Nulla condimentum, nulla ac mattis commodo, est mi imperdiet odio, sed elementum orci velit quis est. Vestibulum quis justo nibh. Ut egestas tellus eu diam commodo vulputate. Vestibulum suscipit, tellus non auctor bibendum, metus nisi hendrerit elit, ut euismod magna ipsum sed tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin varius mi augue, sed auctor eros sagittis sed. Phasellus vehicula ex ut venenatis sagittis. Vivamus volutpat euismod lorem. Aliquam tempor quam orci. Cras ut nulla metus. Donec tempor, leo a tempor venenatis, enim ipsum aliquam tellus, id faucibus turpis magna quis ante. Ut sit amet volutpat nisl, at venenatis tellus. Praesent auctor ut risus at accumsan. Vestibulum venenatis viverra tristique. Duis a velit ornare, ultricies ipsum eu, vehicula felis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec et purus consequat, fringilla augue ut, rhoncus nisi. Duis semper metus nunc, non egestas tellus semper et. Suspendisse congue tempus nunc, id vulputate neque consectetur in. Pellentesque rhoncus, justo at dignissim feugiat, ex ligula gravida lacus, ut varius augue libero eget ante. `; console.log(lorem.length); const msg = fromString( `${lorem}` ); export const roundTrip = new Suite({ async before() { await libsodiumWrapper.ready; await jsNaCl.setInstance(); console.log('\nRound-trip Box Encryption (long message)'); } }); roundTrip.add({ name: "libsodium", fun: () => libsodium(msg) }); roundTrip.add({ name: "tweetnacl", fun: () => tweetnacl(msg) }); roundTrip.add({ name: "js-nacl", fun: () => jsnacl(msg) }); ================================================ FILE: benchmarks/crypto/src/bench/round-trip.ts ================================================ import { Suite } from "asyncmark"; import libsodiumWrapper from "libsodium-wrappers"; import * as jsNaCl from "../lib/js-nacl"; import { libsodium, tweetnacl, jsnacl } from "../lib/round-trip-implementations"; const msg = Uint8Array.from([104, 101, 108, 101, 111]); // hello export const roundTrip = new Suite({ async before() { await libsodiumWrapper.ready; await jsNaCl.setInstance(); console.log("\nRound-trip Box Encryption (short message)"); } }); roundTrip.add({ name: "libsodium", fun: () => libsodium(msg) }); roundTrip.add({ name: "tweetnacl", fun: () => tweetnacl(msg) }); roundTrip.add({ name: "js-nacl", fun: () => jsnacl(msg) }); ================================================ FILE: benchmarks/crypto/src/bench/stringConvension.ts ================================================ import { Suite } from "asyncmark"; import libsodiumWrapper from "libsodium-wrappers"; import * as jsNaCl from "../lib/js-nacl"; import { libsodium, tweetnacl, jsnacl, } from "../lib/utils"; const msg = Uint8Array.from([104, 101, 108, 101, 111]); // hello export const stringConversion = new Suite({ async before() { await libsodiumWrapper.ready; await jsNaCl.setInstance(); console.log("\nround trip uint8 <-> string encode + decode"); } }); stringConversion.add({ name: "libsodium", fun: async () => { libsodium.fromString(await libsodium.toString(msg)); } }); stringConversion.add({ name: "tweet-nacl", fun: async () => { tweetnacl.fromString(await tweetnacl.toString(msg)); } }); stringConversion.add({ name: "js-nacl", fun: async () => { jsnacl.fromString(await jsnacl.toString(msg)); } }); ================================================ FILE: benchmarks/crypto/src/index.ts ================================================ import { roundTrip } from './bench/round-trip'; import { keyGeneration } from './bench/key-generation'; import { nonceGeneration } from './bench/nonce-generation'; import { roundTrip as roundTripLong } from './bench/round-trip-long'; import { base64 } from './bench/base64'; import { stringConversion } from './bench/stringConvension'; import { hex } from './bench/hex'; async function runBenchmark() { // encryption-related things await roundTrip.run(); await keyGeneration.run(); await nonceGeneration.run(); await roundTripLong.run(); // conversion await base64.run(); await stringConversion.run(); await hex.run(); } console.log(` Box Encryption Libraries: Name | Size (min + gzip) -------------|------------------ tweet-nacl | 10.4 kB libsodium | 192.1 kB js-nacl | 212.9 kB `); runBenchmark(); ================================================ FILE: benchmarks/crypto/src/lib/js-nacl.ts ================================================ import NaClFactory, { Nacl } from 'js-nacl'; import { concat } from '../utils'; export let nacl: Nacl; export function setInstance(): Promise { return new Promise((resolve, reject) => { // These apis are.... not good NaClFactory.instantiate((instance: Nacl) => { nacl = instance; resolve(nacl); }); }); } export function generateAsymmetricKeys() { return nacl.crypto_box_keypair(); } export function generateNonce(): Uint8Array { return nacl.crypto_box_random_nonce(); } export function encryptFor( message: Uint8Array, recipientPublicKey: Uint8Array, senderPrivateKey: Uint8Array ): Uint8Array { const nonce = generateNonce(); const ciphertext = nacl.crypto_box(message, nonce, recipientPublicKey, senderPrivateKey); return concat(nonce, ciphertext); } export function decryptFrom( ciphertextWithNonce: Uint8Array, senderPublicKey: Uint8Array, recipientPrivateKey: Uint8Array ): Uint8Array { const [nonce, ciphertext] = splitNonceFromMessage(ciphertextWithNonce); const decrypted = nacl.crypto_box_open(ciphertext, nonce, senderPublicKey, recipientPrivateKey); return decrypted as Uint8Array; } export function splitNonceFromMessage( messageWithNonce: Uint8Array ): [Uint8Array, Uint8Array] { const bytes = nacl.crypto_box_NONCEBYTES; const nonce = messageWithNonce.slice(0, bytes); const message = messageWithNonce.slice(bytes, messageWithNonce.length); return [nonce, message]; } export function toHex(array: Uint8Array): string { return nacl.to_hex(array); } export function fromHex(hex: string): Uint8Array { return nacl.from_hex(hex); } // export async function toBase64(array: Uint8Array): Promise { // return utils.encodeBase64(array); // } // export async function fromBase64(base64: string): Promise { // return utils.decodeBase64(base64); // } export function fromString(str: string): Uint8Array { return nacl.encode_utf8(str); } export const toUint8Array = fromString; export function toString(uint8Array: Uint8Array): string { return nacl.decode_utf8(uint8Array); } ================================================ FILE: benchmarks/crypto/src/lib/libsodium.ts ================================================ import libsodiumWrapper, { KeyPair } from 'libsodium-wrappers'; import { concat } from '../utils'; export async function libsodium(): Promise { const sodium = (libsodiumWrapper as any); await sodium.ready; return sodium as typeof libsodiumWrapper; } export async function genericHash(arr: Uint8Array): Promise { const sodium = await libsodium(); return sodium.crypto_generichash(32, arr); } export async function derivePublicKey(privateKey: Uint8Array): Promise { const sodium = await libsodium(); return sodium.crypto_scalarmult_base(privateKey); } export async function randomBytes(length: number): Promise { const sodium = await libsodium(); return sodium.randombytes_buf(length); } export async function generateNonce(): Promise { const sodium = await libsodium(); return await randomBytes(sodium.crypto_box_NONCEBYTES); } export async function generateAsymmetricKeys(): Promise { const sodium = await libsodium(); return sodium.crypto_box_keypair(); } export async function generateSymmetricKey(): Promise { const sodium = await libsodium(); return await randomBytes(sodium.crypto_box_SECRETKEYBYTES); } export async function encryptFor( message: Uint8Array, recipientPublicKey: Uint8Array, senderPrivateKey: Uint8Array ): Promise { const sodium = await libsodium(); const nonce = await generateNonce(); const ciphertext = sodium.crypto_box_easy(message, nonce, recipientPublicKey, senderPrivateKey); return concat(nonce, ciphertext); } export async function decryptFrom( ciphertextWithNonce: Uint8Array, senderPublicKey: Uint8Array, recipientPrivateKey: Uint8Array ): Promise { const sodium = await libsodium(); const [nonce, ciphertext] = await splitNonceFromMessage(ciphertextWithNonce); const decrypted = sodium.crypto_box_open_easy( ciphertext, nonce, senderPublicKey, recipientPrivateKey ); return decrypted; } export async function splitNonceFromMessage( messageWithNonce: Uint8Array ): Promise<[Uint8Array, Uint8Array]> { const sodium = await libsodium(); const bytes = sodium.crypto_box_NONCEBYTES; const nonce = messageWithNonce.slice(0, bytes); const message = messageWithNonce.slice(bytes, messageWithNonce.length); return [nonce, message]; } export function toHex(array: Uint8Array): string { return libsodiumWrapper.to_hex(array); } export function fromHex(hex: string): Uint8Array { return libsodiumWrapper.from_hex(hex); } export async function toBase64(array: Uint8Array): Promise { const sodium = await libsodium(); return sodium.to_base64(array, sodium.base64_variants.ORIGINAL); } export async function fromBase64(base64: string): Promise { const sodium = await libsodium(); return sodium.from_base64(base64, sodium.base64_variants.ORIGINAL); } export function fromString(str: string): Uint8Array { return libsodiumWrapper.from_string(str); } export const toUint8Array = fromString; export function toString(uint8Array: Uint8Array): string { return libsodiumWrapper.to_string(uint8Array); } ================================================ FILE: benchmarks/crypto/src/lib/round-trip-implementations.ts ================================================ import * as libsodiumjs from "../lib/libsodium"; import * as tweetNaCl from "../lib/tweet-nacl"; import * as jsNaCl from "../lib/js-nacl"; export async function libsodium(msg: Uint8Array) { const receiver = await libsodiumjs.generateAsymmetricKeys(); const sender = await libsodiumjs.generateAsymmetricKeys(); const cipherText = await libsodiumjs.encryptFor( msg, receiver.publicKey, sender.privateKey ); const decrypted = await libsodiumjs.decryptFrom( cipherText, sender.publicKey, receiver.privateKey ); ensureEquality("libsodium", msg, decrypted, libsodiumjs.toString); } export async function tweetnacl(msg: Uint8Array) { const receiver = tweetNaCl.generateAsymmetricKeys(); const sender = tweetNaCl.generateAsymmetricKeys(); const cipherText = await tweetNaCl.encryptFor( msg, receiver.publicKey, sender.secretKey ); const decrypted = await tweetNaCl.decryptFrom( cipherText, sender.publicKey, receiver.secretKey ); ensureEquality("tweetnacl", msg, decrypted, tweetNaCl.toString); } export async function jsnacl(msg: Uint8Array) { const receiver = jsNaCl.generateAsymmetricKeys(); const sender = jsNaCl.generateAsymmetricKeys(); const cipherText = jsNaCl.encryptFor(msg, receiver.boxPk, sender.boxSk); const decrypted = await jsNaCl.decryptFrom( cipherText, sender.boxPk, receiver.boxSk ); ensureEquality("jsnacl", msg, decrypted, jsNaCl.nacl.decode_utf8); } function ensureEquality( label: string, a: Uint8Array, b: Uint8Array, toString: any ) { const as = toString(a); const bs = toString(b); if (as !== bs) { throw new Error(` message was not encrypted and/or decrypted properly. Expected ${as} to equal ${bs} `); } } ================================================ FILE: benchmarks/crypto/src/lib/tweet-nacl.ts ================================================ import * as nacl from 'tweetnacl'; import * as utils from 'tweetnacl-util'; import { concat } from '../utils'; export function generateAsymmetricKeys() { return nacl.box.keyPair(); } export function generateNonce(): Uint8Array { return nacl.randomBytes(nacl.box.nonceLength); } export function encryptFor( message: Uint8Array, recipientPublicKey: Uint8Array, senderPrivateKey: Uint8Array ): Uint8Array { const nonce = generateNonce(); const ciphertext = nacl.box(message, nonce, recipientPublicKey, senderPrivateKey); return concat(nonce, ciphertext); } export function decryptFrom( ciphertextWithNonce: Uint8Array, senderPublicKey: Uint8Array, recipientPrivateKey: Uint8Array ): Uint8Array { const [nonce, ciphertext] = splitNonceFromMessage(ciphertextWithNonce); const decrypted = nacl.box.open(ciphertext, nonce, senderPublicKey, recipientPrivateKey); return decrypted as Uint8Array; } export function splitNonceFromMessage( messageWithNonce: Uint8Array ): [Uint8Array, Uint8Array] { const bytes = nacl.box.nonceLength; const nonce = messageWithNonce.slice(0, bytes); const message = messageWithNonce.slice(bytes, messageWithNonce.length); return [nonce, message]; } // export function toHex(array: Uint8Array): string { // return libsodiumWrapper.to_hex(array); // } // export function fromHex(hex: string): Uint8Array { // return libsodiumWrapper.from_hex(hex); // } export async function toBase64(array: Uint8Array): Promise { return utils.encodeBase64(array); } export async function fromBase64(base64: string): Promise { return utils.decodeBase64(base64); } export function fromString(str: string): Uint8Array { return utils.decodeUTF8(str); } export const toUint8Array = fromString; export function toString(uint8Array: Uint8Array): string { return utils.encodeUTF8(uint8Array); } ================================================ FILE: benchmarks/crypto/src/lib/utils.ts ================================================ import * as libsodumFns from './libsodium'; import * as tweetnaclFns from './tweet-nacl'; import * as jsNaclFns from './js-nacl'; export const libsodium = { toHex: libsodumFns.toHex, fromHex: libsodumFns.fromHex, toBase64: libsodumFns.toBase64, fromBase64: libsodumFns.fromBase64, fromString: libsodumFns.fromString, toString: libsodumFns.toString, }; export const tweetnacl = { toBase64: tweetnaclFns.toBase64, fromBase64: tweetnaclFns.fromBase64, fromString: tweetnaclFns.fromString, toString: tweetnaclFns.toString, } export const jsnacl = { toHex: jsNaclFns.toHex, fromHex: jsNaclFns.fromHex, fromString: jsNaclFns.fromString, toString: jsNaclFns.toString, } ================================================ FILE: benchmarks/crypto/src/utils.ts ================================================ import libsodiumWrapper from 'libsodium-wrappers'; export async function wrapCatch(task) { try { await task(); } catch(e) { console.error(e); throw e; } } export function fromString(str: string): Uint8Array { // return new TextEncoder().encode(str); return libsodiumWrapper.from_string(str); } export function toString(data: Uint8Array): string { return libsodiumWrapper.to_string(data); } export function concat(arr1: Uint8Array, arr2: Uint8Array): Uint8Array { const result = new Uint8Array(arr1.length + arr2.length); result.set(arr1, 0); result.set(arr2, arr1.length); return result; } ================================================ FILE: benchmarks/crypto/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2017", "allowJs": true, "experimentalDecorators": true, "moduleResolution": "node", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "noEmitOnError": false, "noEmit": true, "inlineSourceMap": true, "inlineSources": true, "noImplicitAny": true, "noImplicitThis": true, "noImplicitReturns": false, "alwaysStrict": true, "strictNullChecks": true, "strictPropertyInitialization": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, "baseUrl": ".", "module": "es6", "paths": { "lib/*": [ "src/lib/*" ], "bench/*": [ "src/bench/*" ], "*": [ "src*" ] } }, "include": [ "src" ] } ================================================ FILE: benchmarks/emoji-replace/.babelrc.js ================================================ module.exports = { presets: [ ['@babel/env', { targets: { node: '10' } }], '@babel/preset-typescript', ], plugins: [ '@babel/plugin-transform-modules-commonjs', '@babel/plugin-syntax-dynamic-import', '@babel/plugin-proposal-class-properties', ['@babel/plugin-proposal-decorators', { legacy: true }], '@babel/plugin-transform-runtime', ] }; ================================================ FILE: benchmarks/emoji-replace/.gitignore ================================================ /build ================================================ FILE: benchmarks/emoji-replace/package.json ================================================ { "name": "emoji-replace-benchmarks", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "build": "babel ./src --out-dir ./build --extensions '.ts'", "start": "node build/index.js", "bench": "yarn build && yarn start", "debug": "node --inspect-brk=9229 build/index.js" }, "dependencies": { "emojis": "^1.0.10" }, "devDependencies": { "@babel/cli": "7.6.2", "@babel/core": "7.6.2", "@babel/node": "7.6.2", "@babel/plugin-proposal-class-properties": "7.5.5", "@babel/plugin-proposal-decorators": "7.6.0", "@babel/plugin-syntax-dynamic-import": "7.2.0", "@babel/plugin-transform-modules-commonjs": "7.6.0", "@babel/plugin-transform-runtime": "7.6.2", "@babel/preset-env": "7.6.2", "@babel/preset-typescript": "7.6.0", "@babel/register": "7.6.2", "@babel/runtime": "7.6.2", "@types/js-nacl": "1.2.0", "@types/libsodium-wrappers": "0.7.5", "@types/node": "12.7.11", "asyncmark": "0.3.1", "typescript": "3.6.3" } } ================================================ FILE: benchmarks/emoji-replace/run ================================================ #!/bin/bash yarn yarn build yarn start ================================================ FILE: benchmarks/emoji-replace/src/bench/-utils.ts ================================================ import { Suite } from "asyncmark"; import { unicode } from "emojis"; export function assertEq(expected: T, actual: T, msg: string) { if (expected !== actual) { throw new Error(` ${ msg } expected: ${actual} to equal ${expected} `); } } interface Args { originalString: string; expected: string; benchName: string; } export function generateEmojisBench({ originalString, expected, benchName }: Args) { function directReplace(str: string) { return unicode(str); } function condition(str: string) { if (str.includes(":")) { return unicode(str); } return str; } const EMOJI_REGEX = /:[^:]+:/; function regexTest(str: string) { if (EMOJI_REGEX.test(str)) { return unicode(str); } return str; } function regexMatch(str: string) { if (str.match(EMOJI_REGEX)) { return unicode(str); } return str; } const bench = new Suite({ async before() { assertEq( expected, directReplace(originalString), `${benchName}: direct replace failed` ); assertEq(expected, condition(originalString), `${benchName}: condition failed`); assertEq(expected, regexTest(originalString), `${benchName}: condition failed`); console.log(`\n -- ${benchName} -- `); } }); bench.add({ name: "direct replace ", fun: () => { directReplace(directReplace(originalString)) } }); bench.add({ name: "condition for ", fun: () => { condition(condition(originalString)) } }); bench.add({ name: "regex test ", fun: () => { regexTest(regexTest(originalString)) } }); bench.add({ name: "regex match ", fun: () => { regexMatch(regexMatch(originalString)) } }); return bench; } ================================================ FILE: benchmarks/emoji-replace/src/bench/long.ts ================================================ import { generateEmojisBench } from "./-utils"; const benchName = "long"; const originalString = ` I :heart: the :scream: emoji. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. :smile: Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. I :heart: the :scream: emoji. `; const expected = ` I ❤️ the 😱 emoji. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 😄 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. I ❤️ the 😱 emoji. `; export const bench = generateEmojisBench({ originalString, expected, benchName }); ================================================ FILE: benchmarks/emoji-replace/src/bench/micro.ts ================================================ import { generateEmojisBench } from "./-utils"; const originalString = ":scream:"; const expected = "😱"; const benchName = "micro"; export const bench = generateEmojisBench({ originalString, expected, benchName }); ================================================ FILE: benchmarks/emoji-replace/src/bench/short.ts ================================================ import { generateEmojisBench } from "./-utils"; const originalString = "I :heart: the :scream: emoji."; const expected = "I ❤️ the 😱 emoji."; const benchName = "short"; export const bench = generateEmojisBench({ originalString, expected, benchName }); ================================================ FILE: benchmarks/emoji-replace/src/index.ts ================================================ import { bench as short } from "./bench/short"; import { bench as micro } from "./bench/micro"; import { bench as long } from "./bench/long"; async function runBenchmark() { await micro.run(); await short.run(); await long.run(); } console.log(` Emoji Libraries: Name | Size (min + gzip) -------------|------------------ emojis | 376 Bytes `); runBenchmark(); ================================================ FILE: benchmarks/emoji-replace/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2017", "allowJs": true, "experimentalDecorators": true, "moduleResolution": "node", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "noEmitOnError": false, "noEmit": true, "inlineSourceMap": true, "inlineSources": true, "noImplicitAny": true, "noImplicitThis": true, "noImplicitReturns": false, "alwaysStrict": true, "strictNullChecks": true, "strictPropertyInitialization": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, "baseUrl": ".", "module": "es6", "paths": { "lib/*": [ "src/lib/*" ], "bench/*": [ "src/bench/*" ], "*": [ "src*" ] } }, "include": [ "src" ] } ================================================ FILE: benchmarks/emoji-replace/types/emojis.d.ts ================================================ export function unicode(input: string): string; export function html(input: string): string; ================================================ FILE: client/android-wrapper/.gitignore ================================================ # Built application files *.apk *.ap_ *.aab # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Gradle files .gradle/ build/ release/ appmaker.keystore # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # IntelliJ *.iml .idea/workspace.xml .idea/tasks.xml .idea/gradle.xml .idea/assetWizardSettings.xml .idea/dictionaries .idea/libraries .idea/caches # Android Studio 3 in .gitignore file. .idea/caches/build_file_checksums.ser .idea/modules.xml # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. #*.jks #*.keystore # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild # Google Services (e.g. APIs or Firebase) # google-services.json # Freeline freeline.py freeline/ freeline_project_description.json # fastlane fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output fastlane/readme.md # Version control vcs.xml # lint lint/intermediates/ lint/generated/ lint/outputs/ lint/tmp/ # lint/reports/ ================================================ FILE: client/android-wrapper/CONTRIBUTING.md ================================================ # How to become a contributor and submit your own code ## Contributor License Agreements We'd love to accept your sample apps and patches! Before we can take them, we have to jump a couple of legal hurdles. Please fill out either the individual or corporate Contributor License Agreement (CLA). * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA] (https://developers.google.com/open-source/cla/individual). * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA] (https://developers.google.com/open-source/cla/corporate). Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. ## Contributing A Patch 1. Submit an issue describing your proposed change to the repo in question. 1. The repo owner will respond to your issue promptly. 1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 1. Fork the desired repo, develop and test your code changes. 1. Ensure that your code adheres to the existing style in the sample to which you are contributing. Refer to the [Google Cloud Platform Samples Style Guide] (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the recommended coding standards for this organization. 1. Ensure that your code has an appropriate set of unit tests which all pass. 1. Submit a pull request. ================================================ FILE: client/android-wrapper/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product 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 NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of 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 reason of your accepting any such warranty or additional liability. ================================================ FILE: client/android-wrapper/README.md ================================================ # SVGOMG / Trusted Web Activity This project uses the [Trusted Web Activities](https://developers.google.com/web/updates/2017/10/using-twa) technology to wrap [SVGOMG](https://jakearchibald.github.io/svgomg/) in an Android Application. ## Running the Demo 1. Clone the project `` git clone https://github.com/GoogleChromeLabs/svgomg-twa.git `` 2. Import the Project into Android Studio, using File > New > Import Project, and select the folder to which the project was cloned. 3. Run the Project (Ctrl+R) ### Enabling Debug TWAs require [Digital AssetLinks](https://developers.google.com/digital-asset-links/) to be setup on both the application and on the website, in order to enable the validation that allows Chrome to open the page in full-screen. For security reasons, the signing key compatible with the setup on https://svgomg.firebaseapp.com/ is not committed with the sample code. It is possible to setup Chrome to skip validation on device to enable testing. Here are the 2 steps required to achieve this: 1. Enable Chrome to accept command-line parameters: On the Android Device, go to the Chrome version being used to test the TWA and navigate to `chrome://flags`. Search for a setting called `Enable commmand line on non-rooted devices` and change it to `Enabled`. Restarting the browser *multiple* times may be required. 2. Create an Android file with the command-line parameters that allow skipping the TWA validation. Add a file at `/data/local/tmp/chrome-command-line`, with the content `_ --disable-digital-asset-link-verification-for-url="https://svgomg.firebaseapp.com"`. Make sure there's not newline at the end of the line, or it may break the launcher. For convenience, a shell script that creates this file is available in this repository. Run it by executing `./enable-debug.sh https://svgomg.firebaseapp.com`. To debug a different PWA, execute the script with a different host: `./enable-debug.sh https://example.com` ## License ``` Copyright 2015 Google, Inc. Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ================================================ FILE: client/android-wrapper/app/build.gradle ================================================ /* * Copyright 2019 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * appicon_ios_android.png */ apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "io.emberclear" minSdkVersion 16 targetSdkVersion 28 versionCode 343 versionName "1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" manifestPlaceholders = [ // The hostname is used when building the intent-filter, so the TWA is able to // handle Intents to open https://svgomg.firebaseapp.com. hostName: "io.emberclear", defaultUrl: "https://emberclear.io", launcherName: "emberclear", // This variable below expresses the relationship between the app and the site, // as documented in the TWA documentation at // https://developers.google.com/web/updates/2017/10/using-twa#set_up_digital_asset_links_in_an_android_app // and is injected into the AndroidManifest.xml assetStatements: '[{ "relation": ["delegate_permission/common.handle_all_urls"], ' + '"target": {"namespace": "web", "site": "https://emberclear.io"}}]' ] } signingConfigs { release { if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) { storeFile file(MYAPP_RELEASE_STORE_FILE) storePassword MYAPP_RELEASE_STORE_PASSWORD keyAlias MYAPP_RELEASE_KEY_ALIAS keyPassword MYAPP_RELEASE_KEY_PASSWORD } } } buildTypes { release { minifyEnabled false signingConfig signingConfigs.release } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.github.GoogleChrome:custom-tabs-client:e446d08014' } ================================================ FILE: client/android-wrapper/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: client/android-wrapper/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: client/android-wrapper/build.gradle ================================================ /* * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() // Jitpack is currently being used to publish beta versions of the TWA Support Library. // This will change in the future to use the same approach as other Android Support // Libraries. maven { url "https://jitpack.io" } } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: client/android-wrapper/enable-debug.sh ================================================ #!/usr/bin/env bash # # Copyright 2019 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Check if at least 1 argument is provided if [[ $# -eq 0 ]] then echo "Usage: 'enable-debug.sh '" exit 1 fi # Invokes ADB and creates the file with the command line adb shell "echo '_ --disable-digital-asset-link-verification-for-url=\"$1\"' > /data/local/tmp/chrome-command-line" ================================================ FILE: client/android-wrapper/gradle/wrapper/gradle-wrapper.properties ================================================ #Mon Feb 18 11:18:13 EST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip ================================================ FILE: client/android-wrapper/gradle/wrapper/gradle.properties ================================================ ================================================ FILE: client/android-wrapper/gradle.properties ================================================ MYAPP_RELEASE_STORE_FILE=appmaker.keystore MYAPP_RELEASE_KEY_ALIAS=appmaker-store-Uy9EZlEN6ZDfet5g0KRX MYAPP_RELEASE_STORE_PASSWORD=oz963614f MYAPP_RELEASE_KEY_PASSWORD=n7kqya6cy ================================================ FILE: client/android-wrapper/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: client/android-wrapper/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: client/android-wrapper/settings.gradle ================================================ include ':app' ================================================ FILE: client/web/.eslintignore ================================================ **/declarations/ **/concat-stats-for/ **/dist/ **/tmp/ /node_modules/ **/node_modules/ **/public/ **/vendor/ ================================================ FILE: client/web/.eslintrc.js ================================================ 'use strict'; const { configs } = require('@nullvoxpopuli/eslint-configs'); module.exports = configs.node(); ================================================ FILE: client/web/.gitignore ================================================ .eslintcache .stylelintcache tsconfig.tsbuildinfo ================================================ FILE: client/web/.prettierignore ================================================ **/blueprints/*/files/**/*.js **/bip39/wordlists/english.ts ================================================ FILE: client/web/.prettierrc.js ================================================ 'use strict'; module.exports = { singleQuote: true, trailingComma: 'es5', printWidth: 100, semi: true, bracketSpacing: true, endOfLine: 'lf', tabs: false, tabWidth: 2, }; ================================================ FILE: client/web/.stylelintignore ================================================ # Projects without CSS smoke-tests/ # Ignore supporting directories to improve scan time **/concat-stats-for/** **/dummy/** **/dist/** **/node_modules/** node_modules/** **/tmp/** **/public/** **/vendor/** ================================================ FILE: client/web/.template-lintrc.js ================================================ 'use strict'; module.exports = require('@emberclear/config/.template-lintrc'); ================================================ FILE: client/web/.vim/coc-settings.json ================================================ { "cSpell.words": [ "Encryptable", "Parallelizable", "REFAN", "REFLAT", "Serializable", "Synthwave", "ciphertext", "cond", "emberclear", "esbuild", "keypair", "klass", "nacl", "outfile", "prismjs", "sourcemap", "tmpl" ] } ================================================ FILE: client/web/.vscode/settings.json ================================================ { "typescript.tsdk": "node_modules/typescript/lib" } ================================================ FILE: client/web/addons/crypto/.editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: client/web/addons/crypto/.ember-cli ================================================ { /** Ember CLI sends analytics information by default. The data is completely anonymous, but there are times when you might want to disable this behavior. Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false } ================================================ FILE: client/web/addons/crypto/.eslintignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/crypto/.eslintrc.js ================================================ 'use strict'; const { configs } = require('@nullvoxpopuli/eslint-configs'); module.exports = configs.ember(); ================================================ FILE: client/web/addons/crypto/.gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /.env* /.pnp* /.sass-cache /connect.lock /coverage/ /libpeerconnection.log /npm-debug.log* /testem.log /yarn-error.log # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/crypto/.npmignore ================================================ # compiled output /dist/ /tmp/ # dependencies /bower_components/ # misc /.bowerrc /.editorconfig /.ember-cli /.env* /.eslintignore /.eslintrc.js /.git/ /.gitignore /.template-lintrc.js /.travis.yml /.watchmanconfig /bower.json /config/ember-try.js /CONTRIBUTING.md /ember-cli-build.js /testem.js /tests/ /yarn.lock .gitkeep # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/crypto/.template-lintrc.js ================================================ 'use strict'; module.exports = require('@emberclear/config/.template-lintrc'); ================================================ FILE: client/web/addons/crypto/.watchmanconfig ================================================ { "ignore_dirs": ["tmp", "dist"] } ================================================ FILE: client/web/addons/crypto/CONTRIBUTING.md ================================================ # How To Contribute ## Installation * `git clone ` * `cd crypto` * `yarn install` ## Linting * `yarn lint:hbs` * `yarn lint:js` * `yarn lint:js --fix` ## Running tests * `ember test` – Runs the test suite on the current Ember version * `ember test --server` – Runs the test suite in "watch mode" * `ember try:each` – Runs the test suite against multiple Ember versions ## Running the dummy application * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). ================================================ FILE: client/web/addons/crypto/LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: client/web/addons/crypto/README.md ================================================ crypto ============================================================================== [Short description of the addon.] Compatibility ------------------------------------------------------------------------------ * Ember.js v3.16 or above * Ember CLI v2.13 or above * Node.js v10 or above Installation ------------------------------------------------------------------------------ ``` ember install crypto ``` Usage ------------------------------------------------------------------------------ [Longer description of how to use the addon in apps.] Contributing ------------------------------------------------------------------------------ See the [Contributing](CONTRIBUTING.md) guide for details. License ------------------------------------------------------------------------------ This project is licensed under the [MIT License](LICENSE.md). ================================================ FILE: client/web/addons/crypto/addon/-private/types.ts ================================================ import type { handleMessage } from '../workers/crypto/messages'; export interface WorkerLike { postMessage: typeof handleMessage; _worker?: Worker; } export type WorkerRegistry = { [path: string]: WorkerLike }; ================================================ FILE: client/web/addons/crypto/addon/connector.ts ================================================ import type { WorkersService } from '@emberclear/crypto'; import type { WorkerLike } from '@emberclear/crypto/-private/types'; import type { EncryptedMessage, KeyPair, KeyPublic, Serializable } from '@emberclear/crypto/types'; type Args = { workerService: WorkersService; keys?: KeyPair; }; const Action = { // Generation LOGIN: 0, GENERATE_KEYS: 1, DECRYPT_FROM_SOCKET: 2, ENCRYPT_FOR_SOCKET: 3, GENERATE_SIGNING_KEYS: 4, SIGN: 5, OPEN_SIGNED: 6, HASH: 7, // Conversions MNEMONIC_FROM_PRIVATE_KEY: 50, // TODO: should find a way to not need these DERIVE_PUBLIC_KEY: 100, DERIVE_PUBLIC_SIGNING_KEY: 101, } as const; export default class CryptoConnector { getWorker: () => WorkerLike; keys: KeyPair; constructor({ workerService, keys }: Args) { let { privateKey, publicKey } = keys || ({} as KeyPair); this.getWorker = workerService.getCryptoWorker; this.keys = { privateKey, publicKey }; } async login(mnemonic: string) { let worker = this.getWorker(); return await worker.postMessage({ action: Action.LOGIN, args: [mnemonic], }); } async mnemonicFromNaClBoxPrivateKey(key?: Uint8Array) { let worker = this.getWorker(); return await worker.postMessage({ action: Action.MNEMONIC_FROM_PRIVATE_KEY, args: [key || this.keys.publicKey], }); } async generateKeys() { let worker = this.getWorker(); return await worker.postMessage({ action: Action.GENERATE_KEYS, args: [], }); } async generateSigningKeys() { let worker = this.getWorker(); return await worker.postMessage({ action: Action.GENERATE_SIGNING_KEYS, args: [], }); } async derivePublicKey(privateKey: Uint8Array) { let worker = this.getWorker(); return await worker.postMessage({ action: Action.DERIVE_PUBLIC_KEY, args: [privateKey], }); } async derivePublicSigningKey(privateSigningKey: Uint8Array) { let worker = this.getWorker(); return await worker.postMessage({ action: Action.DERIVE_PUBLIC_SIGNING_KEY, args: [privateSigningKey], }); } async encryptForSocket(payload: Serializable, { publicKey }: KeyPublic) { let worker = this.getWorker(); return await worker.postMessage({ action: Action.ENCRYPT_FOR_SOCKET, args: [payload, { publicKey }, { privateKey: this.keys.privateKey }], }); } async decryptFromSocket(socketData: EncryptedMessage) { let worker = this.getWorker(); return (await worker.postMessage({ action: Action.DECRYPT_FROM_SOCKET, args: [socketData, this.keys.privateKey], })) as Promise; } async sign(message: Uint8Array, senderPrivateKey: Uint8Array) { let worker = this.getWorker(); return await worker.postMessage({ action: Action.SIGN, args: [message, senderPrivateKey], }); } async openSigned(signedMessage: Uint8Array, senderPublicKey: Uint8Array) { let worker = this.getWorker(); return await worker.postMessage({ action: Action.OPEN_SIGNED, args: [signedMessage, senderPublicKey], }); } async hash(message: Uint8Array) { let worker = this.getWorker(); return await worker.postMessage({ action: Action.HASH, args: [message], }); } } ================================================ FILE: client/web/addons/crypto/addon/index.ts ================================================ export { default as CryptoConnector } from './connector'; export { default as WorkersService } from './services/workers'; export type { KeyPair, KeyPrivate, KeyPublic, SigningKeyPair, SigningKeyPrivate, SigningKeyPublic, } from './types'; ================================================ FILE: client/web/addons/crypto/addon/services/workers.ts ================================================ import { action } from '@ember/object'; import Service from '@ember/service'; import { PWBHost } from 'promise-worker-bi'; import type { WorkerLike, WorkerRegistry } from '@emberclear/crypto/-private/types'; export const CRYPTO_PATH = '/workers/crypto'; export const NETWORKING_PATH = '/workers/networking'; export default class WorkersService extends Service { registry: WorkerRegistry = {}; @action getCryptoWorker() { return this.getWorker(CRYPTO_PATH); } @action getNetworkingWorker() { return this.getWorker(NETWORKING_PATH); } protected getWorker(path: string): WorkerLike { if (this.registry[path]) return this.registry[path]; // eslint-disable-next-line @typescript-eslint/no-explicit-any let worker = new Worker(`${path}${(window as any).ASSET_FINGERPRINT_HASH || ''}.js`); let promiseWorker = new PWBHost(worker); // promiseWorker._hostIDQueue = undefined; if (!promiseWorker) { throw new Error('failed to create promiseWorker?'); } promiseWorker.register(function (message: string) { console.info(`Received message in ${path}: `, message); }); promiseWorker.registerError(function (err: Error) { console.error(`Error in ${path}: `, err); }); this.registry[path] = promiseWorker as WorkerLike; return this.registry[path]; } willDestroy() { Object.values(this.registry).forEach((promiseWorker) => { promiseWorker._worker?.terminate(); }); } } // DO NOT DELETE: this is how TypeScript knows how to look up your services. declare module '@ember/service' { interface Registry { workers: WorkersService; } } ================================================ FILE: client/web/addons/crypto/addon/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations", "paths": { "@emberclear/crypto": ["."], "@emberclear/crypto/*": ["./*"], "*": ["../types/*"] } }, "include": [".", "../types"], "references": [ { "path": "../../../libraries/questionably-typed" } ] } ================================================ FILE: client/web/addons/crypto/addon/types.ts ================================================ export type KeyPublic = { publicKey: Uint8Array }; export type KeyPrivate = { privateKey: Uint8Array }; export type KeyPair = KeyPublic & KeyPrivate; export type SigningKeyPublic = { publicSigningKey: Uint8Array }; export type SigningKeyPrivate = { privateSigningKey: Uint8Array }; export type SigningKeyPair = SigningKeyPublic & SigningKeyPrivate; export interface EncryptedMessage { // recipient uid: string; // ciphertext message: string; } export type Serializable = | string | number | boolean | null | undefined | Date | Serializable[] | { [key: string]: Serializable }; export interface EncryptableObject { [key: string]: Serializable; } ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/actions.ts ================================================ import { naclBoxPrivateKeyFromMnemonic } from './utils/mnemonic'; import { derivePublicKey, generateSigningKeys } from './utils/nacl'; import type { KeyPair, SigningKeyPair } from '@emberclear/crypto/types'; export async function login(mnemonic: string): Promise { let privateKey = await naclBoxPrivateKeyFromMnemonic(mnemonic); let publicKey = await derivePublicKey(privateKey); let { publicSigningKey, privateSigningKey } = await generateSigningKeys(); return { publicKey, privateKey, publicSigningKey, privateSigningKey }; } ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/index.ts ================================================ import { PWBWorker } from 'promise-worker-bi'; import { handleMessage } from './messages'; import type { CryptoMessage } from './messages'; let promiseWorker = new PWBWorker(); promiseWorker.register(function (message: CryptoMessage) { return handleMessage(message); }); ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/messages.ts ================================================ import { login } from './actions'; import { mnemonicFromNaClBoxPrivateKey } from './utils/mnemonic'; import { derivePublicKey, derivePublicSigningKey, generateAsymmetricKeys, generateSigningKeys, hash, openSigned, sign, } from './utils/nacl'; import { decryptFromSocket, encryptForSocket } from './utils/socket'; const Action = { // Generation LOGIN: 0, GENERATE_KEYS: 1, DECRYPT_FROM_SOCKET: 2, ENCRYPT_FOR_SOCKET: 3, GENERATE_SIGNING_KEYS: 4, SIGN: 5, OPEN_SIGNED: 6, HASH: 7, // Conversions MNEMONIC_FROM_PRIVATE_KEY: 50, // TODO: should find a way to not need these DERIVE_PUBLIC_KEY: 100, DERIVE_PUBLIC_SIGNING_KEY: 101, } as const; export type API = { [Action.LOGIN]: typeof login; [Action.GENERATE_KEYS]: typeof generateAsymmetricKeys; [Action.GENERATE_SIGNING_KEYS]: typeof generateSigningKeys; [Action.DECRYPT_FROM_SOCKET]: typeof decryptFromSocket; [Action.ENCRYPT_FOR_SOCKET]: typeof encryptForSocket; [Action.SIGN]: typeof sign; [Action.OPEN_SIGNED]: typeof openSigned; [Action.HASH]: typeof hash; [Action.DERIVE_PUBLIC_KEY]: typeof derivePublicKey; [Action.DERIVE_PUBLIC_SIGNING_KEY]: typeof derivePublicSigningKey; [Action.MNEMONIC_FROM_PRIVATE_KEY]: typeof mnemonicFromNaClBoxPrivateKey; }; export type CryptoMessage = { action: keyof API; args: Parameters; }; export type Message = { action: Action; args: Args }; export function handleMessage( message: Message> ): ReturnType { const { action, args } = message as TODO; switch (action) { case Action.LOGIN: return (login as TODO)(...args); case Action.GENERATE_KEYS: return (generateAsymmetricKeys as TODO)(); case Action.GENERATE_SIGNING_KEYS: return (generateSigningKeys as TODO)(); case Action.DECRYPT_FROM_SOCKET: return (decryptFromSocket as TODO)(...args); case Action.ENCRYPT_FOR_SOCKET: return (encryptForSocket as TODO)(...args); case Action.SIGN: return (sign as TODO)(...args); case Action.OPEN_SIGNED: return (openSigned as TODO)(...args); case Action.HASH: return (hash as TODO)(...args); case Action.DERIVE_PUBLIC_KEY: return (derivePublicKey as TODO)(...args); case Action.DERIVE_PUBLIC_SIGNING_KEY: return (derivePublicSigningKey as TODO)(...args); case Action.MNEMONIC_FROM_PRIVATE_KEY: return (mnemonicFromNaClBoxPrivateKey as TODO)(...args); default: throw new Error(`unknown message for crypto worker: ${action}`); } } ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/utils/array.ts ================================================ export function concat(arr1: Uint8Array, arr2: Uint8Array): Uint8Array { const result = new Uint8Array(arr1.length + arr2.length); result.set(arr1, 0); result.set(arr2, arr1.length); return result; } ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/utils/bip39/wordlists/english.ts ================================================ export const wordlist = [ 'abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract', 'absurd', 'abuse', 'access', 'accident', 'account', 'accuse', 'achieve', 'acid', 'acoustic', 'acquire', 'across', 'act', 'action', 'actor', 'actress', 'actual', 'adapt', 'add', 'addict', 'address', 'adjust', 'admit', 'adult', 'advance', 'advice', 'aerobic', 'affair', 'afford', 'afraid', 'again', 'age', 'agent', 'agree', 'ahead', 'aim', 'air', 'airport', 'aisle', 'alarm', 'album', 'alcohol', 'alert', 'alien', 'all', 'alley', 'allow', 'almost', 'alone', 'alpha', 'already', 'also', 'alter', 'always', 'amateur', 'amazing', 'among', 'amount', 'amused', 'analyst', 'anchor', 'ancient', 'anger', 'angle', 'angry', 'animal', 'ankle', 'announce', 'annual', 'another', 'answer', 'antenna', 'antique', 'anxiety', 'any', 'apart', 'apology', 'appear', 'apple', 'approve', 'april', 'arch', 'arctic', 'area', 'arena', 'argue', 'arm', 'armed', 'armor', 'army', 'around', 'arrange', 'arrest', 'arrive', 'arrow', 'art', 'artefact', 'artist', 'artwork', 'ask', 'aspect', 'assault', 'asset', 'assist', 'assume', 'asthma', 'athlete', 'atom', 'attack', 'attend', 'attitude', 'attract', 'auction', 'audit', 'august', 'aunt', 'author', 'auto', 'autumn', 'average', 'avocado', 'avoid', 'awake', 'aware', 'away', 'awesome', 'awful', 'awkward', 'axis', 'baby', 'bachelor', 'bacon', 'badge', 'bag', 'balance', 'balcony', 'ball', 'bamboo', 'banana', 'banner', 'bar', 'barely', 'bargain', 'barrel', 'base', 'basic', 'basket', 'battle', 'beach', 'bean', 'beauty', 'because', 'become', 'beef', 'before', 'begin', 'behave', 'behind', 'believe', 'below', 'belt', 'bench', 'benefit', 'best', 'betray', 'better', 'between', 'beyond', 'bicycle', 'bid', 'bike', 'bind', 'biology', 'bird', 'birth', 'bitter', 'black', 'blade', 'blame', 'blanket', 'blast', 'bleak', 'bless', 'blind', 'blood', 'blossom', 'blouse', 'blue', 'blur', 'blush', 'board', 'boat', 'body', 'boil', 'bomb', 'bone', 'bonus', 'book', 'boost', 'border', 'boring', 'borrow', 'boss', 'bottom', 'bounce', 'box', 'boy', 'bracket', 'brain', 'brand', 'brass', 'brave', 'bread', 'breeze', 'brick', 'bridge', 'brief', 'bright', 'bring', 'brisk', 'broccoli', 'broken', 'bronze', 'broom', 'brother', 'brown', 'brush', 'bubble', 'buddy', 'budget', 'buffalo', 'build', 'bulb', 'bulk', 'bullet', 'bundle', 'bunker', 'burden', 'burger', 'burst', 'bus', 'business', 'busy', 'butter', 'buyer', 'buzz', 'cabbage', 'cabin', 'cable', 'cactus', 'cage', 'cake', 'call', 'calm', 'camera', 'camp', 'can', 'canal', 'cancel', 'candy', 'cannon', 'canoe', 'canvas', 'canyon', 'capable', 'capital', 'captain', 'car', 'carbon', 'card', 'cargo', 'carpet', 'carry', 'cart', 'case', 'cash', 'casino', 'castle', 'casual', 'cat', 'catalog', 'catch', 'category', 'cattle', 'caught', 'cause', 'caution', 'cave', 'ceiling', 'celery', 'cement', 'census', 'century', 'cereal', 'certain', 'chair', 'chalk', 'champion', 'change', 'chaos', 'chapter', 'charge', 'chase', 'chat', 'cheap', 'check', 'cheese', 'chef', 'cherry', 'chest', 'chicken', 'chief', 'child', 'chimney', 'choice', 'choose', 'chronic', 'chuckle', 'chunk', 'churn', 'cigar', 'cinnamon', 'circle', 'citizen', 'city', 'civil', 'claim', 'clap', 'clarify', 'claw', 'clay', 'clean', 'clerk', 'clever', 'click', 'client', 'cliff', 'climb', 'clinic', 'clip', 'clock', 'clog', 'close', 'cloth', 'cloud', 'clown', 'club', 'clump', 'cluster', 'clutch', 'coach', 'coast', 'coconut', 'code', 'coffee', 'coil', 'coin', 'collect', 'color', 'column', 'combine', 'come', 'comfort', 'comic', 'common', 'company', 'concert', 'conduct', 'confirm', 'congress', 'connect', 'consider', 'control', 'convince', 'cook', 'cool', 'copper', 'copy', 'coral', 'core', 'corn', 'correct', 'cost', 'cotton', 'couch', 'country', 'couple', 'course', 'cousin', 'cover', 'coyote', 'crack', 'cradle', 'craft', 'cram', 'crane', 'crash', 'crater', 'crawl', 'crazy', 'cream', 'credit', 'creek', 'crew', 'cricket', 'crime', 'crisp', 'critic', 'crop', 'cross', 'crouch', 'crowd', 'crucial', 'cruel', 'cruise', 'crumble', 'crunch', 'crush', 'cry', 'crystal', 'cube', 'culture', 'cup', 'cupboard', 'curious', 'current', 'curtain', 'curve', 'cushion', 'custom', 'cute', 'cycle', 'dad', 'damage', 'damp', 'dance', 'danger', 'daring', 'dash', 'daughter', 'dawn', 'day', 'deal', 'debate', 'debris', 'decade', 'december', 'decide', 'decline', 'decorate', 'decrease', 'deer', 'defense', 'define', 'defy', 'degree', 'delay', 'deliver', 'demand', 'demise', 'denial', 'dentist', 'deny', 'depart', 'depend', 'deposit', 'depth', 'deputy', 'derive', 'describe', 'desert', 'design', 'desk', 'despair', 'destroy', 'detail', 'detect', 'develop', 'device', 'devote', 'diagram', 'dial', 'diamond', 'diary', 'dice', 'diesel', 'diet', 'differ', 'digital', 'dignity', 'dilemma', 'dinner', 'dinosaur', 'direct', 'dirt', 'disagree', 'discover', 'disease', 'dish', 'dismiss', 'disorder', 'display', 'distance', 'divert', 'divide', 'divorce', 'dizzy', 'doctor', 'document', 'dog', 'doll', 'dolphin', 'domain', 'donate', 'donkey', 'donor', 'door', 'dose', 'double', 'dove', 'draft', 'dragon', 'drama', 'drastic', 'draw', 'dream', 'dress', 'drift', 'drill', 'drink', 'drip', 'drive', 'drop', 'drum', 'dry', 'duck', 'dumb', 'dune', 'during', 'dust', 'dutch', 'duty', 'dwarf', 'dynamic', 'eager', 'eagle', 'early', 'earn', 'earth', 'easily', 'east', 'easy', 'echo', 'ecology', 'economy', 'edge', 'edit', 'educate', 'effort', 'egg', 'eight', 'either', 'elbow', 'elder', 'electric', 'elegant', 'element', 'elephant', 'elevator', 'elite', 'else', 'embark', 'embody', 'embrace', 'emerge', 'emotion', 'employ', 'empower', 'empty', 'enable', 'enact', 'end', 'endless', 'endorse', 'enemy', 'energy', 'enforce', 'engage', 'engine', 'enhance', 'enjoy', 'enlist', 'enough', 'enrich', 'enroll', 'ensure', 'enter', 'entire', 'entry', 'envelope', 'episode', 'equal', 'equip', 'era', 'erase', 'erode', 'erosion', 'error', 'erupt', 'escape', 'essay', 'essence', 'estate', 'eternal', 'ethics', 'evidence', 'evil', 'evoke', 'evolve', 'exact', 'example', 'excess', 'exchange', 'excite', 'exclude', 'excuse', 'execute', 'exercise', 'exhaust', 'exhibit', 'exile', 'exist', 'exit', 'exotic', 'expand', 'expect', 'expire', 'explain', 'expose', 'express', 'extend', 'extra', 'eye', 'eyebrow', 'fabric', 'face', 'faculty', 'fade', 'faint', 'faith', 'fall', 'false', 'fame', 'family', 'famous', 'fan', 'fancy', 'fantasy', 'farm', 'fashion', 'fat', 'fatal', 'father', 'fatigue', 'fault', 'favorite', 'feature', 'february', 'federal', 'fee', 'feed', 'feel', 'female', 'fence', 'festival', 'fetch', 'fever', 'few', 'fiber', 'fiction', 'field', 'figure', 'file', 'film', 'filter', 'final', 'find', 'fine', 'finger', 'finish', 'fire', 'firm', 'first', 'fiscal', 'fish', 'fit', 'fitness', 'fix', 'flag', 'flame', 'flash', 'flat', 'flavor', 'flee', 'flight', 'flip', 'float', 'flock', 'floor', 'flower', 'fluid', 'flush', 'fly', 'foam', 'focus', 'fog', 'foil', 'fold', 'follow', 'food', 'foot', 'force', 'forest', 'forget', 'fork', 'fortune', 'forum', 'forward', 'fossil', 'foster', 'found', 'fox', 'fragile', 'frame', 'frequent', 'fresh', 'friend', 'fringe', 'frog', 'front', 'frost', 'frown', 'frozen', 'fruit', 'fuel', 'fun', 'funny', 'furnace', 'fury', 'future', 'gadget', 'gain', 'galaxy', 'gallery', 'game', 'gap', 'garage', 'garbage', 'garden', 'garlic', 'garment', 'gas', 'gasp', 'gate', 'gather', 'gauge', 'gaze', 'general', 'genius', 'genre', 'gentle', 'genuine', 'gesture', 'ghost', 'giant', 'gift', 'giggle', 'ginger', 'giraffe', 'girl', 'give', 'glad', 'glance', 'glare', 'glass', 'glide', 'glimpse', 'globe', 'gloom', 'glory', 'glove', 'glow', 'glue', 'goat', 'goddess', 'gold', 'good', 'goose', 'gorilla', 'gospel', 'gossip', 'govern', 'gown', 'grab', 'grace', 'grain', 'grant', 'grape', 'grass', 'gravity', 'great', 'green', 'grid', 'grief', 'grit', 'grocery', 'group', 'grow', 'grunt', 'guard', 'guess', 'guide', 'guilt', 'guitar', 'gun', 'gym', 'habit', 'hair', 'half', 'hammer', 'hamster', 'hand', 'happy', 'harbor', 'hard', 'harsh', 'harvest', 'hat', 'have', 'hawk', 'hazard', 'head', 'health', 'heart', 'heavy', 'hedgehog', 'height', 'hello', 'helmet', 'help', 'hen', 'hero', 'hidden', 'high', 'hill', 'hint', 'hip', 'hire', 'history', 'hobby', 'hockey', 'hold', 'hole', 'holiday', 'hollow', 'home', 'honey', 'hood', 'hope', 'horn', 'horror', 'horse', 'hospital', 'host', 'hotel', 'hour', 'hover', 'hub', 'huge', 'human', 'humble', 'humor', 'hundred', 'hungry', 'hunt', 'hurdle', 'hurry', 'hurt', 'husband', 'hybrid', 'ice', 'icon', 'idea', 'identify', 'idle', 'ignore', 'ill', 'illegal', 'illness', 'image', 'imitate', 'immense', 'immune', 'impact', 'impose', 'improve', 'impulse', 'inch', 'include', 'income', 'increase', 'index', 'indicate', 'indoor', 'industry', 'infant', 'inflict', 'inform', 'inhale', 'inherit', 'initial', 'inject', 'injury', 'inmate', 'inner', 'innocent', 'input', 'inquiry', 'insane', 'insect', 'inside', 'inspire', 'install', 'intact', 'interest', 'into', 'invest', 'invite', 'involve', 'iron', 'island', 'isolate', 'issue', 'item', 'ivory', 'jacket', 'jaguar', 'jar', 'jazz', 'jealous', 'jeans', 'jelly', 'jewel', 'job', 'join', 'joke', 'journey', 'joy', 'judge', 'juice', 'jump', 'jungle', 'junior', 'junk', 'just', 'kangaroo', 'keen', 'keep', 'ketchup', 'key', 'kick', 'kid', 'kidney', 'kind', 'kingdom', 'kiss', 'kit', 'kitchen', 'kite', 'kitten', 'kiwi', 'knee', 'knife', 'knock', 'know', 'lab', 'label', 'labor', 'ladder', 'lady', 'lake', 'lamp', 'language', 'laptop', 'large', 'later', 'latin', 'laugh', 'laundry', 'lava', 'law', 'lawn', 'lawsuit', 'layer', 'lazy', 'leader', 'leaf', 'learn', 'leave', 'lecture', 'left', 'leg', 'legal', 'legend', 'leisure', 'lemon', 'lend', 'length', 'lens', 'leopard', 'lesson', 'letter', 'level', 'liar', 'liberty', 'library', 'license', 'life', 'lift', 'light', 'like', 'limb', 'limit', 'link', 'lion', 'liquid', 'list', 'little', 'live', 'lizard', 'load', 'loan', 'lobster', 'local', 'lock', 'logic', 'lonely', 'long', 'loop', 'lottery', 'loud', 'lounge', 'love', 'loyal', 'lucky', 'luggage', 'lumber', 'lunar', 'lunch', 'luxury', 'lyrics', 'machine', 'mad', 'magic', 'magnet', 'maid', 'mail', 'main', 'major', 'make', 'mammal', 'man', 'manage', 'mandate', 'mango', 'mansion', 'manual', 'maple', 'marble', 'march', 'margin', 'marine', 'market', 'marriage', 'mask', 'mass', 'master', 'match', 'material', 'math', 'matrix', 'matter', 'maximum', 'maze', 'meadow', 'mean', 'measure', 'meat', 'mechanic', 'medal', 'media', 'melody', 'melt', 'member', 'memory', 'mention', 'menu', 'mercy', 'merge', 'merit', 'merry', 'mesh', 'message', 'metal', 'method', 'middle', 'midnight', 'milk', 'million', 'mimic', 'mind', 'minimum', 'minor', 'minute', 'miracle', 'mirror', 'misery', 'miss', 'mistake', 'mix', 'mixed', 'mixture', 'mobile', 'model', 'modify', 'mom', 'moment', 'monitor', 'monkey', 'monster', 'month', 'moon', 'moral', 'more', 'morning', 'mosquito', 'mother', 'motion', 'motor', 'mountain', 'mouse', 'move', 'movie', 'much', 'muffin', 'mule', 'multiply', 'muscle', 'museum', 'mushroom', 'music', 'must', 'mutual', 'myself', 'mystery', 'myth', 'naive', 'name', 'napkin', 'narrow', 'nasty', 'nation', 'nature', 'near', 'neck', 'need', 'negative', 'neglect', 'neither', 'nephew', 'nerve', 'nest', 'net', 'network', 'neutral', 'never', 'news', 'next', 'nice', 'night', 'noble', 'noise', 'nominee', 'noodle', 'normal', 'north', 'nose', 'notable', 'note', 'nothing', 'notice', 'novel', 'now', 'nuclear', 'number', 'nurse', 'nut', 'oak', 'obey', 'object', 'oblige', 'obscure', 'observe', 'obtain', 'obvious', 'occur', 'ocean', 'october', 'odor', 'off', 'offer', 'office', 'often', 'oil', 'okay', 'old', 'olive', 'olympic', 'omit', 'once', 'one', 'onion', 'online', 'only', 'open', 'opera', 'opinion', 'oppose', 'option', 'orange', 'orbit', 'orchard', 'order', 'ordinary', 'organ', 'orient', 'original', 'orphan', 'ostrich', 'other', 'outdoor', 'outer', 'output', 'outside', 'oval', 'oven', 'over', 'own', 'owner', 'oxygen', 'oyster', 'ozone', 'pact', 'paddle', 'page', 'pair', 'palace', 'palm', 'panda', 'panel', 'panic', 'panther', 'paper', 'parade', 'parent', 'park', 'parrot', 'party', 'pass', 'patch', 'path', 'patient', 'patrol', 'pattern', 'pause', 'pave', 'payment', 'peace', 'peanut', 'pear', 'peasant', 'pelican', 'pen', 'penalty', 'pencil', 'people', 'pepper', 'perfect', 'permit', 'person', 'pet', 'phone', 'photo', 'phrase', 'physical', 'piano', 'picnic', 'picture', 'piece', 'pig', 'pigeon', 'pill', 'pilot', 'pink', 'pioneer', 'pipe', 'pistol', 'pitch', 'pizza', 'place', 'planet', 'plastic', 'plate', 'play', 'please', 'pledge', 'pluck', 'plug', 'plunge', 'poem', 'poet', 'point', 'polar', 'pole', 'police', 'pond', 'pony', 'pool', 'popular', 'portion', 'position', 'possible', 'post', 'potato', 'pottery', 'poverty', 'powder', 'power', 'practice', 'praise', 'predict', 'prefer', 'prepare', 'present', 'pretty', 'prevent', 'price', 'pride', 'primary', 'print', 'priority', 'prison', 'private', 'prize', 'problem', 'process', 'produce', 'profit', 'program', 'project', 'promote', 'proof', 'property', 'prosper', 'protect', 'proud', 'provide', 'public', 'pudding', 'pull', 'pulp', 'pulse', 'pumpkin', 'punch', 'pupil', 'puppy', 'purchase', 'purity', 'purpose', 'purse', 'push', 'put', 'puzzle', 'pyramid', 'quality', 'quantum', 'quarter', 'question', 'quick', 'quit', 'quiz', 'quote', 'rabbit', 'raccoon', 'race', 'rack', 'radar', 'radio', 'rail', 'rain', 'raise', 'rally', 'ramp', 'ranch', 'random', 'range', 'rapid', 'rare', 'rate', 'rather', 'raven', 'raw', 'razor', 'ready', 'real', 'reason', 'rebel', 'rebuild', 'recall', 'receive', 'recipe', 'record', 'recycle', 'reduce', 'reflect', 'reform', 'refuse', 'region', 'regret', 'regular', 'reject', 'relax', 'release', 'relief', 'rely', 'remain', 'remember', 'remind', 'remove', 'render', 'renew', 'rent', 'reopen', 'repair', 'repeat', 'replace', 'report', 'require', 'rescue', 'resemble', 'resist', 'resource', 'response', 'result', 'retire', 'retreat', 'return', 'reunion', 'reveal', 'review', 'reward', 'rhythm', 'rib', 'ribbon', 'rice', 'rich', 'ride', 'ridge', 'rifle', 'right', 'rigid', 'ring', 'riot', 'ripple', 'risk', 'ritual', 'rival', 'river', 'road', 'roast', 'robot', 'robust', 'rocket', 'romance', 'roof', 'rookie', 'room', 'rose', 'rotate', 'rough', 'round', 'route', 'royal', 'rubber', 'rude', 'rug', 'rule', 'run', 'runway', 'rural', 'sad', 'saddle', 'sadness', 'safe', 'sail', 'salad', 'salmon', 'salon', 'salt', 'salute', 'same', 'sample', 'sand', 'satisfy', 'satoshi', 'sauce', 'sausage', 'save', 'say', 'scale', 'scan', 'scare', 'scatter', 'scene', 'scheme', 'school', 'science', 'scissors', 'scorpion', 'scout', 'scrap', 'screen', 'script', 'scrub', 'sea', 'search', 'season', 'seat', 'second', 'secret', 'section', 'security', 'seed', 'seek', 'segment', 'select', 'sell', 'seminar', 'senior', 'sense', 'sentence', 'series', 'service', 'session', 'settle', 'setup', 'seven', 'shadow', 'shaft', 'shallow', 'share', 'shed', 'shell', 'sheriff', 'shield', 'shift', 'shine', 'ship', 'shiver', 'shock', 'shoe', 'shoot', 'shop', 'short', 'shoulder', 'shove', 'shrimp', 'shrug', 'shuffle', 'shy', 'sibling', 'sick', 'side', 'siege', 'sight', 'sign', 'silent', 'silk', 'silly', 'silver', 'similar', 'simple', 'since', 'sing', 'siren', 'sister', 'situate', 'six', 'size', 'skate', 'sketch', 'ski', 'skill', 'skin', 'skirt', 'skull', 'slab', 'slam', 'sleep', 'slender', 'slice', 'slide', 'slight', 'slim', 'slogan', 'slot', 'slow', 'slush', 'small', 'smart', 'smile', 'smoke', 'smooth', 'snack', 'snake', 'snap', 'sniff', 'snow', 'soap', 'soccer', 'social', 'sock', 'soda', 'soft', 'solar', 'soldier', 'solid', 'solution', 'solve', 'someone', 'song', 'soon', 'sorry', 'sort', 'soul', 'sound', 'soup', 'source', 'south', 'space', 'spare', 'spatial', 'spawn', 'speak', 'special', 'speed', 'spell', 'spend', 'sphere', 'spice', 'spider', 'spike', 'spin', 'spirit', 'split', 'spoil', 'sponsor', 'spoon', 'sport', 'spot', 'spray', 'spread', 'spring', 'spy', 'square', 'squeeze', 'squirrel', 'stable', 'stadium', 'staff', 'stage', 'stairs', 'stamp', 'stand', 'start', 'state', 'stay', 'steak', 'steel', 'stem', 'step', 'stereo', 'stick', 'still', 'sting', 'stock', 'stomach', 'stone', 'stool', 'story', 'stove', 'strategy', 'street', 'strike', 'strong', 'struggle', 'student', 'stuff', 'stumble', 'style', 'subject', 'submit', 'subway', 'success', 'such', 'sudden', 'suffer', 'sugar', 'suggest', 'suit', 'summer', 'sun', 'sunny', 'sunset', 'super', 'supply', 'supreme', 'sure', 'surface', 'surge', 'surprise', 'surround', 'survey', 'suspect', 'sustain', 'swallow', 'swamp', 'swap', 'swarm', 'swear', 'sweet', 'swift', 'swim', 'swing', 'switch', 'sword', 'symbol', 'symptom', 'syrup', 'system', 'table', 'tackle', 'tag', 'tail', 'talent', 'talk', 'tank', 'tape', 'target', 'task', 'taste', 'tattoo', 'taxi', 'teach', 'team', 'tell', 'ten', 'tenant', 'tennis', 'tent', 'term', 'test', 'text', 'thank', 'that', 'theme', 'then', 'theory', 'there', 'they', 'thing', 'this', 'thought', 'three', 'thrive', 'throw', 'thumb', 'thunder', 'ticket', 'tide', 'tiger', 'tilt', 'timber', 'time', 'tiny', 'tip', 'tired', 'tissue', 'title', 'toast', 'tobacco', 'today', 'toddler', 'toe', 'together', 'toilet', 'token', 'tomato', 'tomorrow', 'tone', 'tongue', 'tonight', 'tool', 'tooth', 'top', 'topic', 'topple', 'torch', 'tornado', 'tortoise', 'toss', 'total', 'tourist', 'toward', 'tower', 'town', 'toy', 'track', 'trade', 'traffic', 'tragic', 'train', 'transfer', 'trap', 'trash', 'travel', 'tray', 'treat', 'tree', 'trend', 'trial', 'tribe', 'trick', 'trigger', 'trim', 'trip', 'trophy', 'trouble', 'truck', 'true', 'truly', 'trumpet', 'trust', 'truth', 'try', 'tube', 'tuition', 'tumble', 'tuna', 'tunnel', 'turkey', 'turn', 'turtle', 'twelve', 'twenty', 'twice', 'twin', 'twist', 'two', 'type', 'typical', 'ugly', 'umbrella', 'unable', 'unaware', 'uncle', 'uncover', 'under', 'undo', 'unfair', 'unfold', 'unhappy', 'uniform', 'unique', 'unit', 'universe', 'unknown', 'unlock', 'until', 'unusual', 'unveil', 'update', 'upgrade', 'uphold', 'upon', 'upper', 'upset', 'urban', 'urge', 'usage', 'use', 'used', 'useful', 'useless', 'usual', 'utility', 'vacant', 'vacuum', 'vague', 'valid', 'valley', 'valve', 'van', 'vanish', 'vapor', 'various', 'vast', 'vault', 'vehicle', 'velvet', 'vendor', 'venture', 'venue', 'verb', 'verify', 'version', 'very', 'vessel', 'veteran', 'viable', 'vibrant', 'vicious', 'victory', 'video', 'view', 'village', 'vintage', 'violin', 'virtual', 'virus', 'visa', 'visit', 'visual', 'vital', 'vivid', 'vocal', 'voice', 'void', 'volcano', 'volume', 'vote', 'voyage', 'wage', 'wagon', 'wait', 'walk', 'wall', 'walnut', 'want', 'warfare', 'warm', 'warrior', 'wash', 'wasp', 'waste', 'water', 'wave', 'way', 'wealth', 'weapon', 'wear', 'weasel', 'weather', 'web', 'wedding', 'weekend', 'weird', 'welcome', 'west', 'wet', 'whale', 'what', 'wheat', 'wheel', 'when', 'where', 'whip', 'whisper', 'wide', 'width', 'wife', 'wild', 'will', 'win', 'window', 'wine', 'wing', 'wink', 'winner', 'winter', 'wire', 'wisdom', 'wise', 'wish', 'witness', 'wolf', 'woman', 'wonder', 'wood', 'wool', 'word', 'work', 'world', 'worry', 'worth', 'wrap', 'wreck', 'wrestle', 'wrist', 'write', 'wrong', 'yard', 'year', 'yellow', 'you', 'young', 'youth', 'zebra', 'zero', 'zone', 'zoo', ]; ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/utils/bip39/wordlists/index.ts ================================================ export { wordlist as english } from './english'; ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/utils/mnemonic.ts ================================================ import { english } from './bip39/wordlists'; import { genericHash } from './nacl'; // https://crypto.stackexchange.com/a/50759/8245 // BIP 39 describes the implementation of a mnemonic code or mnemonic sentence // -- a group of easy to remember words -- // for the generation of deterministic wallets. // // Bitcoin private key is not stored in this way, rather seed to prng // which generated the private and public key pair is converted into // mnemonic so that its easy for human to type or remember. // // A list of 2048 words, which is indexed from 0-2047(11 bit information) is used. // 132 bit value (128 bit seed + 4 bit checksum) is divided into 12 chunks of 11 bits each, // then each 11 bit is used to select a word from dictionary. // // for more details see BIP39 // // NOTE: 2^11 = 2048 // NOTE: 2048 = how many words in a bip39 wordlist // NOTE: list is range 0 - 2047 export async function mnemonicFromNaClBoxPrivateKey(privateKey: Uint8Array): Promise { const uint11Array = toUint11Array(privateKey); const words = applyWords(uint11Array); const checksumWord = await computeChecksum(privateKey); return words.join(' ') + ' ' + checksumWord; } export async function naclBoxPrivateKeyFromMnemonic(mnemonic: string): Promise { const words = mnemonic.split(' '); const key = words.slice(0, 24); const checksum = words[words.length - 1]; const nums = key.map((word) => english.indexOf(word)); // verify with the checksum, to see if we need to chop off the last // byte or something const fullResult = toUint8Array(nums); const fullCheck = await computeChecksum(fullResult); // because 256bits doesn't divide by 11, we will sometimes // have a stray 0 at the end of the conversion const shortResult = fullResult.slice(0, fullResult.length - 1); const shortCheck = await computeChecksum(shortResult); // success! if (shortCheck === fullCheck) return shortResult; if (fullCheck === checksum) return fullResult; throw 'Checksum could not validate private key'; } export async function computeChecksum(nums: Uint8Array): Promise { const sum = nums.reduce((acc, v) => acc + v); const arr32 = Uint32Array.from([sum]); const arr8 = new Uint8Array(arr32.buffer, 0, 4); const hashBuffer = await genericHash(arr8); const uint11Hash = toUint11Array(hashBuffer); const words = applyWords(uint11Hash); return words[0]; } function applyWords(nums: number[]): string[] { return nums.map((n) => english[n]); } // https://stackoverflow.com/a/50285590/356849 export function toUint11Array(input: Uint8Array): number[] { let buffer = 0; let numbits = 0; let output = []; for (let i = 0; i < input.length; i++) { // prepend bits to buffer buffer |= input[i] << numbits; numbits += 8; // if there are enough bits, extract 11bit chunk if (numbits >= 11) { // 0x7FF is 2047, the max 11 bit number output.push(buffer & 0x7ff); // drop chunk from buffer buffer = buffer >> 11; numbits -= 11; } } // also output leftover bits if (numbits != 0) { output.push(buffer & 0x7ff); } return output; } // from Uint11Array export function toUint8Array(input: number[]): Uint8Array { let buffer = 0; let numbits = 0; let output: number[] = []; for (let i = 0; i < input.length; i++) { // prepend bits to buffer // buffer increments // 11 -> 3 -> 14 -> 6 -> 17 -> 9 -> 1 -> 12 -> 4 -> 15 buffer |= input[i] << numbits; numbits += 11; // if there are enough bits, extract 8 bit number while (numbits >= 8) { // 0xff is 255 output.push(buffer & 0xff); // drop chunk from buffer buffer = buffer >> 8; numbits -= 8; } } return Uint8Array.from(output); } ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/utils/nacl.ts ================================================ import { blake2b } from 'blakejs'; import nacl from 'tweetnacl'; import { concat } from './array'; export async function genericHash(arr: Uint8Array): Promise { return blake2b(arr, undefined, 32); } export async function derivePublicKey(privateKey: Uint8Array) { const keypair = nacl.box.keyPair.fromSecretKey(privateKey); return keypair.publicKey; } export async function derivePublicSigningKey(privateSigningKey: Uint8Array) { const keyPair = nacl.sign.keyPair.fromSecretKey(privateSigningKey); return keyPair.publicKey; } export async function randomBytes(length: number) { return nacl.randomBytes(length); } export async function sign(message: Uint8Array, senderPrivateKey: Uint8Array): Promise { return nacl.sign(message, senderPrivateKey); } export async function openSigned( signedMessage: Uint8Array, senderPublicKey: Uint8Array ): Promise { return nacl.sign.open(signedMessage, senderPublicKey); } export async function hash(message: Uint8Array): Promise { return nacl.hash(message); } export async function generateNonce() { return nacl.randomBytes(nacl.box.nonceLength); } export async function generateAsymmetricKeys() { const keyPair = nacl.box.keyPair(); return { publicKey: keyPair.publicKey, privateKey: keyPair.secretKey, }; } export async function generateSymmetricKeys() { return nacl.randomBytes(nacl.secretbox.keyLength); } export async function generateSigningKeys() { const keyPair = nacl.sign.keyPair(); return { publicSigningKey: keyPair.publicKey, privateSigningKey: keyPair.secretKey, }; } export async function encryptFor( message: Uint8Array, recipientPublicKey: Uint8Array, senderPrivateKey: Uint8Array ): Promise { const nonce = await generateNonce(); const ciphertext = nacl.box(message, nonce, recipientPublicKey, senderPrivateKey); return concat(nonce, ciphertext); } export async function decryptFrom( ciphertextWithNonce: Uint8Array, senderPublicKey: Uint8Array, recipientPrivateKey: Uint8Array ): Promise { const [nonce, ciphertext] = splitNonceFromMessage(ciphertextWithNonce); const decrypted = nacl.box.open(ciphertext, nonce, senderPublicKey, recipientPrivateKey); return decrypted as Uint8Array; } export function splitNonceFromMessage(messageWithNonce: Uint8Array): [Uint8Array, Uint8Array] { const bytes = nacl.box.nonceLength; const nonce = messageWithNonce.slice(0, bytes); const message = messageWithNonce.slice(bytes, messageWithNonce.length); return [nonce, message]; } ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/utils/socket.ts ================================================ import { decryptFrom, encryptFor } from './nacl'; import { fromBase64, fromHex, toBase64, toString, toUint8Array } from './string-encoding'; import type { EncryptedMessage, KeyPrivate, KeyPublic, Serializable, } from '@emberclear/crypto/types'; export async function encryptForSocket(payload: Serializable, to: KeyPublic, from: KeyPrivate) { const payloadString = JSON.stringify(payload); const payloadBytes = toUint8Array(payloadString); const encryptedMessage = await encryptFor(payloadBytes, to.publicKey, from.privateKey); return await toBase64(encryptedMessage); } export async function decryptFromSocket(socketData: EncryptedMessage, privateKey: Uint8Array) { const { uid, message } = socketData; const senderPublicKey = fromHex(uid); const recipientPrivateKey = privateKey; const decrypted = await decryptMessage(message, senderPublicKey, recipientPrivateKey); return decrypted; } async function decryptMessage( message: string, senderPublicKey: Uint8Array, recipientPrivateKey: Uint8Array ) { const messageBytes = await fromBase64(message); const decrypted = await decryptFrom(messageBytes, senderPublicKey, recipientPrivateKey); // TODO: consider a binary format, instead of // converting to/from string and json const payload = toString(decrypted); const data = JSON.parse(payload); return data; } ================================================ FILE: client/web/addons/crypto/addon/workers/crypto/utils/string-encoding.ts ================================================ import utils from 'tweetnacl-util'; export function toHex(array: Uint8Array): string { return Array.from(array) .map((b) => b.toString(16).padStart(2, '0')) .join(''); } export function fromHex(hex: string): Uint8Array { if (Math.ceil(hex.length / 2) !== hex.length / 2) { throw new Error('hex string is the wrong length'); } const matches = hex.match(/.{1,2}/g) || []; return new Uint8Array(matches.map((byte) => parseInt(byte, 16))); } export async function toBase64(array: Uint8Array): Promise { return utils.encodeBase64(array); } export async function fromBase64(base64: string): Promise { return utils.decodeBase64(base64); } export function fromString(str: string): Uint8Array { return utils.decodeUTF8(str); } export const toUint8Array = fromString; export function toString(uint8Array: Uint8Array): string { return utils.encodeUTF8(uint8Array); } export function ensureUint8Array(text: string | Uint8Array): Uint8Array { if (text.constructor === Uint8Array) { return text as Uint8Array; } return fromString(text as string); } // http://stackoverflow.com/a/39460727 export function base64ToHex(base64: string): string | undefined { if (base64 === undefined) return undefined; // convert to binary, than to hex const raw = atob(base64); let hex = ''; for (let i = 0; i < raw.length; i++) { const hexChar = raw.charCodeAt(i).toString(16); hex += hexChar.length === 2 ? hexChar : `0${hexChar}`; } return hex.toUpperCase(); } ================================================ FILE: client/web/addons/crypto/addon-test-support/index.ts ================================================ import { getContext } from '@ember/test-helpers'; import { CryptoConnector } from '@emberclear/crypto'; import { toHex } from '@emberclear/crypto/workers/crypto/utils/string-encoding'; import type { WorkersService } from '@emberclear/crypto'; import type { TestContext } from 'ember-test-helpers'; export { setupWorkers } from './setup'; // no one use this! // prettier-ignore export const samplePrivateKey = Uint8Array.from([ 43, 191, 106, 38, 141, 42, 151, 128, 227, 93, 124, 214, 166, 222, 144, 176, 162, 181, 203, 27, 39, 18, 37, 173, 2, 189, 139, 8, 181, 8, 171, 45 ]); export async function newCrypto() { const workers = (getContext() as TestContext).owner.lookup('service:workers') as WorkersService; const crypto = new CryptoConnector({ workerService: workers }); let { publicKey, privateKey } = await crypto.generateKeys(); return { publicKey, privateKey, hex: { publicKey: toHex(publicKey), privateKey: toHex(privateKey), }, crypto, }; } ================================================ FILE: client/web/addons/crypto/addon-test-support/setup.ts ================================================ import WorkersService, { CRYPTO_PATH } from '@emberclear/crypto/services/workers'; import { handleMessage } from '@emberclear/crypto/workers/crypto/messages'; import type { TestContext } from 'ember-test-helpers'; /** * * in a test environment, we can't assume a stable connection * to the internet. * * even though the test index.html and web workers live on the same * host, the tests freak out when the internet connection is interrupted. */ export function setupWorkers(hooks: NestedHooks) { hooks.beforeEach(function (this: TestContext) { class WorkersProxy extends WorkersService { getWorker(path: string) { switch (path) { case CRYPTO_PATH: return fakeCrypto; default: throw new Error(`No worker proxy exists for worker: ${path}`); } } } this.owner.register('service:workers', WorkersProxy); }); } const fakeCrypto = { postMessage: handleMessage, }; ================================================ FILE: client/web/addons/crypto/addon-test-support/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations/test-support", "paths": { "@emberclear/crypto": ["../declarations"], "@emberclear/crypto/*": ["../declarations/*"], "@emberclear/crypto/test-support": ["."], "@emberclear/crypto/test-support/*": ["./*"] } }, "references": [ { "path": "../addon" } ] } ================================================ FILE: client/web/addons/crypto/app/services/workers.ts ================================================ export { default } from '@emberclear/crypto/services/workers'; ================================================ FILE: client/web/addons/crypto/config/ember-try.js ================================================ 'use strict'; const getChannelURL = require('ember-source-channel-url'); module.exports = async function () { return { useYarn: true, scenarios: [ { name: 'ember-lts-3.16', npm: { devDependencies: { 'ember-source': '~3.16.0', }, }, }, { name: 'ember-lts-3.20', npm: { devDependencies: { 'ember-source': '~3.20.5', }, }, }, { name: 'ember-release', npm: { devDependencies: { 'ember-source': await getChannelURL('release'), }, }, }, { name: 'ember-beta', npm: { devDependencies: { 'ember-source': await getChannelURL('beta'), }, }, }, { name: 'ember-canary', npm: { devDependencies: { 'ember-source': await getChannelURL('canary'), }, }, }, { name: 'ember-default-with-jquery', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true, }), }, npm: { devDependencies: { '@ember/jquery': '^1.1.0', }, }, }, { name: 'ember-classic', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'application-template-wrapper': true, 'default-async-observers': false, 'template-only-glimmer-components': false, }), }, npm: { ember: { edition: 'classic', }, }, }, ], }; }; ================================================ FILE: client/web/addons/crypto/config/environment.js ================================================ 'use strict'; module.exports = function (/* environment, appConfig */) { return {}; }; ================================================ FILE: client/web/addons/crypto/ember-cli-build.js ================================================ 'use strict'; const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function (defaults) { let app = new EmberAddon(defaults, { // Add options here 'ember-cli-babel': { enableTypeScriptTransform: true, }, }); /* This build file specifies the options for the dummy test app of this addon, located in `/tests/dummy` This build file does *not* influence how the addon or the app using it behave. You most likely want to be modifying `./index.js` or app's build file */ return app.toTree(); }; ================================================ FILE: client/web/addons/crypto/index.js ================================================ 'use strict'; const path = require('path'); const os = require('os'); const fs = require('fs'); const Funnel = require('broccoli-funnel'); const { buildWorkers } = require('./lib/worker-build'); module.exports = { name: require('./package').name, options: { 'ember-cli-babel': { enableTypeScriptTransform: true, }, }, // override isDevelopingAddon() { return true; }, // override treeForPublic() { let buildDir = fs.mkdtempSync(path.join(os.tmpdir(), '@emberclear--crypto--')); let options = { isProduction: true, buildDir, }; // outputs {buildDir}/crypto.js buildWorkers(options); return new Funnel(buildDir, { destDir: 'workers/', }); }, }; ================================================ FILE: client/web/addons/crypto/lib/worker-build.js ================================================ 'use strict'; const path = require('path'); const fs = require('fs'); const esbuild = require('esbuild'); const addonFolder = path.join(__dirname, '..', 'addon'); const workerRoot = path.join(addonFolder, 'workers'); function detectWorkers() { let workers = {}; let dir = fs.readdirSync(workerRoot); for (let i = 0; i < dir.length; i++) { let name = dir[i]; workers[name] = path.join(workerRoot, name, 'index.ts'); } return workers; } function configureWorkerTree({ isProduction, buildDir }) { return ([name, entryPath]) => { esbuild.buildSync({ loader: { '.ts': 'ts' }, entryPoints: [entryPath], bundle: true, outfile: path.join(buildDir, `${name}.js`), format: 'esm', minify: isProduction, sourcemap: !isProduction, // incremental: true, tsconfig: path.join(addonFolder, 'tsconfig.json'), }); }; } function buildWorkers(env) { let inputs = detectWorkers(); let workerBuilder = configureWorkerTree(env); // separate build from ember, will be detached, won't watch Object.entries(inputs).map(workerBuilder); } module.exports = { buildWorkers }; ================================================ FILE: client/web/addons/crypto/package.json ================================================ { "name": "@emberclear/crypto", "version": "0.0.0", "description": "Crypto workers for emberclear-related projects", "keywords": [ "ember-addon" ], "repository": { "url": "https://github.com/NullVoxPopuli/emberclear", "directory": "client/web/addons/crypto" }, "license": "GPL-3.0", "author": "NullVoxPopuli", "directories": { "doc": "doc", "test": "tests" }, "scripts": { "build": "ember build --environment=production", "lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*", "lint:hbs": "ember-template-lint .", "lint:js": "eslint .", "start": "ember serve", "test": "ember test", "test:try-one": "ember try:one", "test:ember-compatibility": "ember try:each" }, "dependencies": { "blakejs": "1.1.0", "ember-auto-import": "1.11.3", "ember-cli-babel": "7.26.6", "ember-cli-htmlbars": "5.7.1", "esbuild": "0.11.23", "promise-worker-bi": "4.0.2", "tweetnacl": "1.0.3", "tweetnacl-util": "0.15.1" }, "devDependencies": { "@ember/optional-features": "^2.0.0", "@emberclear/config": "*", "@nullvoxpopuli/eslint-configs": "1.3.2", "@emberclear/questionably-typed": "*", "@glimmer/component": "1.0.4", "@glimmer/tracking": "1.0.4", "@types/ember": "3.16.5", "@types/ember-qunit": "3.4.13", "@types/ember-resolver": "^5.0.10", "@types/ember__test-helpers": "2.0.0", "@types/qunit": "2.11.1", "@types/rsvp": "4.0.3", "broccoli-asset-rev": "^3.0.0", "ember-cli": "3.26.1", "ember-cli-dependency-checker": "^3.2.0", "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "4.0.2", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", "ember-load-initializers": "2.1.2", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", "ember-resolver": "^8.0.2", "ember-source": "3.26.1", "ember-source-channel-url": "^3.0.0", "ember-try": "^1.4.0", "loader.js": "^4.7.0", "npm-run-all": "^4.1.5", "qunit-dom": "1.6.0", "typescript": "4.3.4" }, "engines": { "node": "14.17.1" }, "typesVersions": { "*": { "*": [ "declarations/*", "declarations/*/index" ] } }, "volta": { "node": "14.17.1", "yarn": "1.22.10" }, "ember": { "edition": "octane" }, "ember-addon": { "configPath": "tests/dummy/config" } } ================================================ FILE: client/web/addons/crypto/testem.js ================================================ 'use strict'; module.exports = require('@emberclear/config/testem'); ================================================ FILE: client/web/addons/crypto/tests/dummy/app/app.ts ================================================ import Application from '@ember/application'; import config from 'dummy/config/environment'; import loadInitializers from 'ember-load-initializers'; import Resolver from 'ember-resolver'; export default class App extends Application { modulePrefix = config.modulePrefix; podModulePrefix = config.podModulePrefix; Resolver = Resolver; } loadInitializers(App, config.modulePrefix); ================================================ FILE: client/web/addons/crypto/tests/dummy/app/components/.gitkeep ================================================ ================================================ FILE: client/web/addons/crypto/tests/dummy/app/config/environment.d.ts ================================================ export default config; /** * Type declarations for * import config from './config/environment' * * For now these need to be managed by the developer * since different ember addons can materialize new entries. */ declare const config: { environment: 'production' | 'test' | 'development'; modulePrefix: string; podModulePrefix: string; locationType: string; rootURL: string; host: string; APP: Record; }; ================================================ FILE: client/web/addons/crypto/tests/dummy/app/controllers/.gitkeep ================================================ ================================================ FILE: client/web/addons/crypto/tests/dummy/app/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/crypto/tests/dummy/app/index.html ================================================ Dummy {{content-for "head"}} {{content-for "head-footer"}} {{content-for "body"}} {{content-for "body-footer"}} ================================================ FILE: client/web/addons/crypto/tests/dummy/app/models/.gitkeep ================================================ ================================================ FILE: client/web/addons/crypto/tests/dummy/app/router.js ================================================ import EmberRouter from '@ember/routing/router'; import config from 'dummy/config/environment'; export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; } Router.map(function () {}); ================================================ FILE: client/web/addons/crypto/tests/dummy/app/routes/.gitkeep ================================================ ================================================ FILE: client/web/addons/crypto/tests/dummy/app/styles/app.css ================================================ ================================================ FILE: client/web/addons/crypto/tests/dummy/app/templates/application.hbs ================================================ {{outlet}} ================================================ FILE: client/web/addons/crypto/tests/dummy/config/ember-cli-update.json ================================================ { "schemaVersion": "1.0.0", "packages": [ { "name": "ember-cli", "version": "3.21.2", "blueprints": [ { "name": "addon", "outputRepo": "https://github.com/ember-cli/ember-addon-output", "codemodsSource": "ember-addon-codemods-manifest@1", "isBaseBlueprint": true, "options": [ "--yarn" ] } ] } ] } ================================================ FILE: client/web/addons/crypto/tests/dummy/config/environment.js ================================================ 'use strict'; module.exports = function (environment) { let ENV = { modulePrefix: 'dummy', environment, rootURL: '/', locationType: 'auto', EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true }, EXTEND_PROTOTYPES: { // Prevent Ember Data from overriding Date.parse. Date: false, }, }, APP: { // Here you can pass flags/options to your application instance // when it is created }, }; if (environment === 'development') { // ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_ACTIVE_GENERATION = true; // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; } if (environment === 'test') { // Testem prefers this... ENV.locationType = 'none'; // keep test console output quieter ENV.APP.LOG_ACTIVE_GENERATION = false; ENV.APP.LOG_VIEW_LOOKUPS = false; ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; } if (environment === 'production') { // here you can enable a production-specific feature } return ENV; }; ================================================ FILE: client/web/addons/crypto/tests/dummy/config/optional-features.json ================================================ { "application-template-wrapper": false, "default-async-observers": true, "jquery-integration": false, "template-only-glimmer-components": true } ================================================ FILE: client/web/addons/crypto/tests/dummy/config/targets.js ================================================ 'use strict'; const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; const isCI = Boolean(process.env.CI); const isProduction = process.env.EMBER_ENV === 'production'; if (isCI || isProduction) { browsers.push('ie 11'); } module.exports = { browsers, }; ================================================ FILE: client/web/addons/crypto/tests/dummy/public/robots.txt ================================================ # http://www.robotstxt.org User-agent: * Disallow: ================================================ FILE: client/web/addons/crypto/tests/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/crypto/tests/index.html ================================================ Dummy Tests {{content-for "head"}} {{content-for "test-head"}} {{content-for "head-footer"}} {{content-for "test-head-footer"}} {{content-for "body"}} {{content-for "test-body"}} {{content-for "body-footer"}} {{content-for "test-body-footer"}} ================================================ FILE: client/web/addons/crypto/tests/integration/.gitkeep ================================================ ================================================ FILE: client/web/addons/crypto/tests/test-helper.ts ================================================ import { setApplication } from '@ember/test-helpers'; import { start } from 'ember-qunit'; import Application from 'dummy/app'; import config from 'dummy/config/environment'; setApplication(Application.create(config.APP)); start(); ================================================ FILE: client/web/addons/crypto/tests/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", // Question: what's the best place for test and dummy declarations to go? They // aren't actually needed for anything other than to satisfy the requirements // for a composite build. "declarationDir": "./dummy/declarations", "paths": { "dummy/tests/*": ["./*"], "dummy/*": ["./dummy/app/*"], "@emberclear/crypto": ["../declarations"], "@emberclear/crypto/*": ["../declarations/*"], "*": ["../types/*"] } }, "include": [ ".", "../types" ], "references": [ { "path": "../addon" }, { "path": "../addon-test-support" } ] } ================================================ FILE: client/web/addons/crypto/tests/unit/services/workers-test.js ================================================ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; module('Unit | Service | workers', function (hooks) { setupTest(hooks); // Replace this with your real tests. test('it exists', function (assert) { let service = this.owner.lookup('service:workers'); assert.ok(service); }); }); ================================================ FILE: client/web/addons/crypto/tests/workers/crypto/mnemonic-test.ts ================================================ import { module, test } from 'qunit'; import { samplePrivateKey } from '@emberclear/crypto/test-support'; import { mnemonicFromNaClBoxPrivateKey, naclBoxPrivateKeyFromMnemonic, toUint8Array, toUint11Array, } from '@emberclear/crypto/workers/crypto/utils/mnemonic'; module('Workers | Crypto | mnemonic', function () { const numbers = { ['32']: new Uint8Array([0x20]), ['64']: new Uint8Array([0x40]), ['2048']: new Uint8Array([0x8, 0]), ['4096']: new Uint8Array([0x10, 0]), ['7331']: new Uint8Array([0xa3, 0x1c]), }; test('mnemonicFromNaClBoxPrivateKey | converts a private key to english', async function (assert) { const result = await mnemonicFromNaClBoxPrivateKey(samplePrivateKey); // prettier-ignore const expected = ` tornado priority nasty potato comic then upper labor suspect kind embody climb hero very decide banana pigeon apple teach master head season hood ability fossil `.replace(/[ \n\r]+/g, ' ').trim(); assert.deepEqual(result, expected); }); test('key can be converted and recovered', async function (assert) { const mnemonic = await mnemonicFromNaClBoxPrivateKey(samplePrivateKey); const result = await naclBoxPrivateKeyFromMnemonic(mnemonic); assert.deepEqual(result, samplePrivateKey); }); test('toUint11Array | converts | 32 (8 bits)', function (assert) { const result = toUint11Array(numbers['32']); const expected = [32]; assert.deepEqual(result, expected); }); test('toUint11Array | converts | 2048 (12 bits)', function (assert) { const result = toUint11Array(numbers['2048']); const expected = [8, 0]; assert.deepEqual(result, expected); }); test('toUint11Array | converts | 4096 (13 bits)', function (assert) { const result = toUint11Array(numbers['4096']); const expected = [16, 0]; assert.deepEqual(result, expected); }); test('toUint11Array | converts | 7331 (13 bits)', function (assert) { const result = toUint11Array(numbers['7331']); const expected = [1187, 3]; assert.deepEqual(result, expected); }); test('toUint11Array | converts | private key', function (assert) { const result = toUint11Array(samplePrivateKey); // prettier-ignore const expected = [ 1835, 1367, 1177, 1350, 370, 1793, 1912, 994, 1750, 980, 579, 344, 858, 1943, 454, 145, 1317, 85, 1780, 1093, 848, 1553, 874, 1 ]; assert.deepEqual(result, expected); }); test('toUint8Array | converts | 32', function (assert) { const result = toUint8Array([32]); const expected = numbers['32']; assert.deepEqual(result, expected); }); test('toUint8Array | converts | 2048', function (assert) { const result = toUint8Array([8, 0]); const expected = numbers['2048']; assert.deepEqual(result, expected); }); test('toUint8Array | converts | 4096', function (assert) { const result = toUint8Array([16, 0]); const expected = numbers['4096']; assert.deepEqual(result, expected); }); test('toUint8Array | converts | 7331', function (assert) { const result = toUint8Array([1187, 3]); const expected = numbers['7331']; assert.deepEqual(result, expected); }); }); ================================================ FILE: client/web/addons/crypto/tests/workers/crypto/nacl-test.ts ================================================ import { module, skip, test } from 'qunit'; import * as nacl from '@emberclear/crypto/workers/crypto/utils/nacl'; module('Workers | Crypto | nacl', function () { skip('libsodium uses wasm', async function (assert) { assert.expect(0); // not using libsodium atm. WASM support seems unstable // (or libsodium is unstable between updates) // const sodium = await nacl.libsodium(); // const isUsingWasm = (sodium as any).libsodium.usingWasm; // assert.ok(isUsingWasm); }); test('generateAsymmetricKeys | works', async function (assert) { const boxKeys = await nacl.generateAsymmetricKeys(); assert.ok(boxKeys.publicKey); assert.ok(boxKeys.privateKey); }); test('generateSigningKeys | works', async function (assert) { const signingKeys = await nacl.generateSigningKeys(); assert.ok(signingKeys.publicSigningKey); assert.ok(signingKeys.privateSigningKey); }); test('encryptFor/decryptFrom | works with Uint8Array', async function (assert) { const receiver = await nacl.generateAsymmetricKeys(); const sender = await nacl.generateAsymmetricKeys(); const msgAsUint8 = Uint8Array.from([104, 101, 108, 108, 111]); // hello const ciphertext = await nacl.encryptFor(msgAsUint8, receiver.publicKey, sender.privateKey); const decrypted = await nacl.decryptFrom(ciphertext, sender.publicKey, receiver.privateKey); assert.deepEqual(msgAsUint8, decrypted); }); test('encryptFor/decryptFrom | works with large data', async function (assert) { const receiver = await nacl.generateAsymmetricKeys(); const sender = await nacl.generateAsymmetricKeys(); let bigMsg: number[] = []; for (let i = 0; i < 128; i++) { bigMsg = bigMsg.concat([104, 101, 108, 108, 111]); } const msgAsUint8 = Uint8Array.from(bigMsg); // hello * 128 = 640 Bytes const ciphertext = await nacl.encryptFor(msgAsUint8, receiver.publicKey, sender.privateKey); const decrypted = await nacl.decryptFrom(ciphertext, sender.publicKey, receiver.privateKey); assert.deepEqual(msgAsUint8, decrypted); }); test('sign/open | works with Uint8Array', async function (assert) { const sender = await nacl.generateSigningKeys(); const msgAsUint8 = Uint8Array.from([104, 101, 108, 108, 111]); // hello const signedText = await nacl.sign(msgAsUint8, sender.privateSigningKey); const openedText = await nacl.openSigned(signedText, sender.publicSigningKey); assert.deepEqual(msgAsUint8, openedText); }); test('sign/open | works with large data', async function (assert) { const sender = await nacl.generateSigningKeys(); let bigMsg: number[] = []; for (let i = 0; i < 128; i++) { bigMsg = bigMsg.concat([104, 101, 108, 108, 111]); } const msgAsUint8 = Uint8Array.from(bigMsg); // hello * 128 = 640 Bytes const signedText = await nacl.sign(msgAsUint8, sender.privateSigningKey); const openedText = await nacl.openSigned(signedText, sender.publicSigningKey); assert.deepEqual(msgAsUint8, openedText); }); test('splitNonceFromMessage | separates the nonce', async function (assert) { // prettier-ignore const msg = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 ]; const messageWithNonce = Uint8Array.from([...msg, 25]); const [nonce, notTheNonce] = await nacl.splitNonceFromMessage(messageWithNonce); assert.deepEqual(nonce, Uint8Array.from(msg)); assert.deepEqual(notTheNonce, Uint8Array.from([25])); }); }); ================================================ FILE: client/web/addons/crypto/tests/workers/crypto/string-encoding-test.ts ================================================ import { module, test } from 'qunit'; import * as stringEncoding from '@emberclear/crypto/workers/crypto/utils/string-encoding'; module('Workers | Crypto | String Encoding', function () { module('toString / fromString', function () { test('converts a sting to and back from uint8', function (assert) { const str = 'hello there'; const uint8Array = stringEncoding.fromString(str); const original = stringEncoding.toString(uint8Array); assert.equal(original, str); }); }); module('toBase64 / fromBase64', function () { test('converts uint8array and back', async function (assert) { const msgAsUint8 = new Uint8Array([0, 1, 2, 3, 4]); const base64 = await stringEncoding.toBase64(msgAsUint8); const result = await stringEncoding.fromBase64(base64); assert.deepEqual(result, msgAsUint8); }); }); module('toHex / fromHex', function () { test('converts uint8array and back', function (assert) { const msgAsUint8 = Uint8Array.from([104, 101, 108, 108, 111]); // hello const hex = stringEncoding.toHex(msgAsUint8); const original = stringEncoding.fromHex(hex); assert.deepEqual(original, msgAsUint8); }); }); module('toString / fromString', function () { test('converts to string and back', function (assert) { const msgAsUint8 = Uint8Array.from([104, 101, 108, 108, 111]); // hello const str = stringEncoding.toString(msgAsUint8); const original = stringEncoding.fromString(str); assert.deepEqual(original, msgAsUint8); }); }); }); ================================================ FILE: client/web/addons/crypto/tsconfig.compiler-options.json ================================================ { // Alias to reduce the number of ../ in paths "extends": "../../config/tsconfig.compiler-options.json" } ================================================ FILE: client/web/addons/crypto/tsconfig.json ================================================ { "files": [], "compilerOptions": { "composite": true }, "exclude": ["declarations"], "references": [ { "path": "addon" }, { "path": "addon-test-support" }, { "path": "tests" } ] } ================================================ FILE: client/web/addons/crypto/types/overrides.d.ts ================================================ import '@emberclear/questionably-typed/overrides'; ================================================ FILE: client/web/addons/crypto/vendor/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/.editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: client/web/addons/encoding/.ember-cli ================================================ { /** Ember CLI sends analytics information by default. The data is completely anonymous, but there are times when you might want to disable this behavior. Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false } ================================================ FILE: client/web/addons/encoding/.eslintignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/encoding/.eslintrc.js ================================================ 'use strict'; const { configs } = require('@nullvoxpopuli/eslint-configs'); module.exports = configs.ember(); ================================================ FILE: client/web/addons/encoding/.gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/ /tmp/ # dependencies /bower_components/ /node_modules/ # misc /.env* /.pnp* /.sass-cache /connect.lock /coverage/ /libpeerconnection.log /npm-debug.log* /testem.log /yarn-error.log # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/encoding/.npmignore ================================================ # compiled output /dist/ /tmp/ # dependencies /bower_components/ # misc /.bowerrc /.editorconfig /.ember-cli /.env* /.eslintignore /.eslintrc.js /.git/ /.gitignore /.template-lintrc.js /.travis.yml /.watchmanconfig /bower.json /config/ember-try.js /CONTRIBUTING.md /ember-cli-build.js /testem.js /tests/ /yarn.lock .gitkeep # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/encoding/.template-lintrc.js ================================================ 'use strict'; module.exports = require('@emberclear/config/.template-lintrc'); ================================================ FILE: client/web/addons/encoding/.travis.yml ================================================ --- language: node_js node_js: # we recommend testing addons with the same minimum supported node version as Ember CLI # so that your addon works for all apps - "10" dist: xenial addons: chrome: stable cache: yarn: true env: global: # See https://git.io/vdao3 for details. - JOBS=1 branches: only: - master # npm version tags - /^v\d+\.\d+\.\d+/ jobs: fast_finish: true allow_failures: - env: EMBER_TRY_SCENARIO=ember-canary include: # runs linting and tests with current locked deps - stage: "Tests" name: "Tests" script: - yarn lint - yarn test:ember - stage: "Additional Tests" name: "Floating Dependencies" install: - yarn install --no-lockfile --non-interactive script: - yarn test:ember # we recommend new addons test the current and previous LTS # as well as latest stable release (bonus points to beta/canary) - env: EMBER_TRY_SCENARIO=ember-lts-3.16 - env: EMBER_TRY_SCENARIO=ember-lts-3.20 - env: EMBER_TRY_SCENARIO=ember-release - env: EMBER_TRY_SCENARIO=ember-beta - env: EMBER_TRY_SCENARIO=ember-canary - env: EMBER_TRY_SCENARIO=ember-default-with-jquery - env: EMBER_TRY_SCENARIO=ember-classic before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH=$HOME/.yarn/bin:$PATH script: - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO ================================================ FILE: client/web/addons/encoding/.watchmanconfig ================================================ { "ignore_dirs": ["tmp", "dist"] } ================================================ FILE: client/web/addons/encoding/CONTRIBUTING.md ================================================ # How To Contribute ## Installation * `git clone ` * `cd encoding` * `yarn install` ## Linting * `yarn lint:hbs` * `yarn lint:js` * `yarn lint:js --fix` ## Running tests * `ember test` – Runs the test suite on the current Ember version * `ember test --server` – Runs the test suite in "watch mode" * `ember try:each` – Runs the test suite against multiple Ember versions ## Running the dummy application * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). ================================================ FILE: client/web/addons/encoding/LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: client/web/addons/encoding/README.md ================================================ encoding ============================================================================== [Short description of the addon.] Compatibility ------------------------------------------------------------------------------ * Ember.js v3.16 or above * Ember CLI v2.13 or above * Node.js v10 or above Installation ------------------------------------------------------------------------------ ``` ember install encoding ``` Usage ------------------------------------------------------------------------------ [Longer description of how to use the addon in apps.] Contributing ------------------------------------------------------------------------------ See the [Contributing](CONTRIBUTING.md) guide for details. License ------------------------------------------------------------------------------ This project is licensed under the [MIT License](LICENSE.md). ================================================ FILE: client/web/addons/encoding/addon/string.ts ================================================ import QRCode from 'qrcode'; export function toHex(array: Uint8Array): string { return Array.from(array) .map((b) => b.toString(16).padStart(2, '0')) .join(''); } export function fromHex(hex: string): Uint8Array { if (Math.ceil(hex.length / 2) !== hex.length / 2) { throw new Error('hex string is the wrong length'); } const matches = hex.match(/.{1,2}/g) || []; return new Uint8Array(matches.map((byte) => parseInt(byte, 16))); } type Serializable = | string | number | boolean | null | undefined | Date | Serializable[] | { [key: string]: Serializable }; export async function convertObjectToQRCodeDataURL>( object: T ): Promise { const str = JSON.stringify(object); return await QRCode.toDataURL(str); } export function convertObjectToUint8Array(object: T): Uint8Array { const str = JSON.stringify(object); return new TextEncoder().encode(str); } export function convertUint8ArrayToObject(array: Uint8Array): T { const str = new TextDecoder().decode(array); return JSON.parse(str); } export function convertObjectToBase64String(object: Serializable): string { const json = JSON.stringify(object); const base64 = btoa(json); return base64; } export function convertBase64StringToObject(base64: string): Serializable { const json = atob(base64); const obj = JSON.parse(json); return obj; } export function objectToDataURL(obj: Serializable): string { const str = JSON.stringify(obj); return `data:text/json;charset=utf-8,${encodeURIComponent(str)}`; } // http://stackoverflow.com/a/39460727 export function base64ToHex(base64: string): string | undefined { if (base64 === undefined) return undefined; // convert to binary, than to hex const raw = atob(base64); let hex = ''; for (let i = 0; i < raw.length; i++) { const hexChar = raw.charCodeAt(i).toString(16); hex += hexChar.length === 2 ? hexChar : `0${hexChar}`; } return hex.toUpperCase(); } ================================================ FILE: client/web/addons/encoding/addon/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations", "paths": { "@emberclear/encoding": ["."], "@emberclear/encoding/*": ["./*"], "*": ["../types/*"] } }, "include": [".", "../types"], "references": [ { "path": "../../../libraries/questionably-typed" } ] } ================================================ FILE: client/web/addons/encoding/config/ember-try.js ================================================ 'use strict'; const getChannelURL = require('ember-source-channel-url'); module.exports = async function () { return { useYarn: true, scenarios: [ { name: 'ember-lts-3.16', npm: { devDependencies: { 'ember-source': '~3.16.0', }, }, }, { name: 'ember-lts-3.20', npm: { devDependencies: { 'ember-source': '~3.20.5', }, }, }, { name: 'ember-release', npm: { devDependencies: { 'ember-source': await getChannelURL('release'), }, }, }, { name: 'ember-beta', npm: { devDependencies: { 'ember-source': await getChannelURL('beta'), }, }, }, { name: 'ember-canary', npm: { devDependencies: { 'ember-source': await getChannelURL('canary'), }, }, }, { name: 'ember-default-with-jquery', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true, }), }, npm: { devDependencies: { '@ember/jquery': '^1.1.0', }, }, }, { name: 'ember-classic', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'application-template-wrapper': true, 'default-async-observers': false, 'template-only-glimmer-components': false, }), }, npm: { ember: { edition: 'classic', }, }, }, ], }; }; ================================================ FILE: client/web/addons/encoding/config/environment.js ================================================ 'use strict'; module.exports = function (/* environment, appConfig */) { return {}; }; ================================================ FILE: client/web/addons/encoding/ember-cli-build.js ================================================ 'use strict'; const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function (defaults) { let app = new EmberAddon(defaults, { // Add options here 'ember-cli-babel': { enableTypeScriptTransform: true, }, }); /* This build file specifies the options for the dummy test app of this addon, located in `/tests/dummy` This build file does *not* influence how the addon or the app using it behave. You most likely want to be modifying `./index.js` or app's build file */ return app.toTree(); }; ================================================ FILE: client/web/addons/encoding/index.js ================================================ 'use strict'; module.exports = { name: require('./package').name, options: { 'ember-cli-babel': { enableTypeScriptTransform: true, }, }, // override isDevelopingAddon() { return true; }, }; ================================================ FILE: client/web/addons/encoding/package.json ================================================ { "name": "@emberclear/encoding", "version": "0.0.0", "description": "The default blueprint for ember-cli addons.", "keywords": [ "ember-addon" ], "repository": { "url": "https://github.com/NullVoxPopuli/emberclear", "directory": "client/web/addons/crypto" }, "license": "GPL-3.0", "author": "NullVoxPopuli", "directories": { "doc": "doc", "test": "tests" }, "scripts": { "build": "ember build --environment=production", "lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*", "lint:hbs": "ember-template-lint .", "lint:js": "eslint .", "start": "ember serve", "test": "ember test", "test:try-one": "ember try:one", "test:ember-compatibility": "ember try:each" }, "dependencies": { "@emberclear/config": "*", "@nullvoxpopuli/eslint-configs": "1.3.2", "ember-auto-import": "^1.11.3", "ember-cli-babel": "7.26.6", "ember-cli-htmlbars": "5.7.1", "qrcode": "1.4.4" }, "devDependencies": { "@ember/optional-features": "^2.0.0", "@emberclear/questionably-typed": "*", "@glimmer/component": "1.0.4", "@glimmer/tracking": "1.0.4", "@types/ember": "3.16.5", "@types/ember-qunit": "3.4.13", "@types/ember-resolver": "^5.0.10", "@types/ember__test-helpers": "2.0.0", "@types/qrcode": "1.4.0", "@types/qunit": "2.11.1", "@types/rsvp": "4.0.3", "broccoli-asset-rev": "^3.0.0", "ember-cli": "3.26.1", "ember-cli-dependency-checker": "^3.2.0", "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "4.0.2", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", "ember-load-initializers": "2.1.2", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", "ember-resolver": "^8.0.2", "ember-source": "3.26.1", "ember-source-channel-url": "^3.0.0", "ember-try": "^1.4.0", "loader.js": "^4.7.0", "npm-run-all": "^4.1.5", "qunit-dom": "1.6.0", "typescript": "4.3.4" }, "engines": { "node": "14.17.1" }, "typesVersions": { "*": { "*": [ "declarations/*", "declarations/*/index" ] } }, "volta": { "node": "14.17.1", "yarn": "1.22.10" }, "ember": { "edition": "octane" }, "ember-addon": { "configPath": "tests/dummy/config" } } ================================================ FILE: client/web/addons/encoding/testem.js ================================================ 'use strict'; module.exports = { test_page: 'tests/index.html?hidepassed', disable_watching: true, launch_in_ci: ['Chrome'], launch_in_dev: ['Chrome'], browser_start_timeout: 120, browser_args: { Chrome: { ci: [ // --no-sandbox is needed when running Chrome inside a container process.env.CI ? '--no-sandbox' : null, '--headless', '--disable-dev-shm-usage', '--disable-software-rasterizer', '--mute-audio', '--remote-debugging-port=0', '--window-size=1440,900', ].filter(Boolean), }, }, }; ================================================ FILE: client/web/addons/encoding/tests/dummy/app/app.ts ================================================ import Application from '@ember/application'; import config from 'dummy/config/environment'; import loadInitializers from 'ember-load-initializers'; import Resolver from 'ember-resolver'; export default class App extends Application { modulePrefix = config.modulePrefix; podModulePrefix = config.podModulePrefix; Resolver = Resolver; } loadInitializers(App, config.modulePrefix); ================================================ FILE: client/web/addons/encoding/tests/dummy/app/components/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/tests/dummy/app/config/environment.d.ts ================================================ export default config; /** * Type declarations for * import config from './config/environment' * * For now these need to be managed by the developer * since different ember addons can materialize new entries. */ declare const config: { environment: 'production' | 'test' | 'development'; modulePrefix: string; podModulePrefix: string; locationType: string; rootURL: string; host: string; APP: Record; }; ================================================ FILE: client/web/addons/encoding/tests/dummy/app/controllers/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/tests/dummy/app/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/tests/dummy/app/index.html ================================================ Dummy {{content-for "head"}} {{content-for "head-footer"}} {{content-for "body"}} {{content-for "body-footer"}} ================================================ FILE: client/web/addons/encoding/tests/dummy/app/models/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/tests/dummy/app/router.js ================================================ import EmberRouter from '@ember/routing/router'; import config from 'dummy/config/environment'; export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; } Router.map(function () {}); ================================================ FILE: client/web/addons/encoding/tests/dummy/app/routes/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/tests/dummy/app/styles/app.css ================================================ ================================================ FILE: client/web/addons/encoding/tests/dummy/app/templates/application.hbs ================================================ {{outlet}} ================================================ FILE: client/web/addons/encoding/tests/dummy/config/ember-cli-update.json ================================================ { "schemaVersion": "1.0.0", "packages": [ { "name": "ember-cli", "version": "3.23.0", "blueprints": [ { "name": "addon", "outputRepo": "https://github.com/ember-cli/ember-addon-output", "codemodsSource": "ember-addon-codemods-manifest@1", "isBaseBlueprint": true, "options": [ "--yarn" ] } ] } ] } ================================================ FILE: client/web/addons/encoding/tests/dummy/config/environment.js ================================================ 'use strict'; module.exports = function (environment) { let ENV = { modulePrefix: 'dummy', environment, rootURL: '/', locationType: 'auto', EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true }, EXTEND_PROTOTYPES: { // Prevent Ember Data from overriding Date.parse. Date: false, }, }, APP: { // Here you can pass flags/options to your application instance // when it is created }, }; if (environment === 'development') { // ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_ACTIVE_GENERATION = true; // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; } if (environment === 'test') { // Testem prefers this... ENV.locationType = 'none'; // keep test console output quieter ENV.APP.LOG_ACTIVE_GENERATION = false; ENV.APP.LOG_VIEW_LOOKUPS = false; ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; } if (environment === 'production') { // here you can enable a production-specific feature } return ENV; }; ================================================ FILE: client/web/addons/encoding/tests/dummy/config/optional-features.json ================================================ { "application-template-wrapper": false, "default-async-observers": true, "jquery-integration": false, "template-only-glimmer-components": true } ================================================ FILE: client/web/addons/encoding/tests/dummy/config/targets.js ================================================ 'use strict'; const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; const isCI = Boolean(process.env.CI); const isProduction = process.env.EMBER_ENV === 'production'; if (isCI || isProduction) { browsers.push('ie 11'); } module.exports = { browsers, }; ================================================ FILE: client/web/addons/encoding/tests/dummy/public/robots.txt ================================================ # http://www.robotstxt.org User-agent: * Disallow: ================================================ FILE: client/web/addons/encoding/tests/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/tests/index.html ================================================ Dummy Tests {{content-for "head"}} {{content-for "test-head"}} {{content-for "head-footer"}} {{content-for "test-head-footer"}} {{content-for "body"}} {{content-for "test-body"}} {{content-for "body-footer"}} {{content-for "test-body-footer"}} ================================================ FILE: client/web/addons/encoding/tests/integration/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/tests/test-helper.js ================================================ import { setApplication } from '@ember/test-helpers'; import { start } from 'ember-qunit'; import Application from 'dummy/app'; import config from 'dummy/config/environment'; setApplication(Application.create(config.APP)); start(); ================================================ FILE: client/web/addons/encoding/tests/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", // Question: what's the best place for test and dummy declarations to go? They // aren't actually needed for anything other than to satisfy the requirements // for a composite build. "declarationDir": "./dummy/declarations", "paths": { "dummy/tests/*": ["./*"], "dummy/*": ["./dummy/app/*"], "@emberclear/encoding": ["../declarations"], "@emberclear/encoding/*": ["../declarations/*"], "*": ["../types/*"] } }, "include": [ ".", "../types" ], "references": [ { "path": "../addon" } /* { "path": "../addon-test-support" } */ ] } ================================================ FILE: client/web/addons/encoding/tests/unit/.gitkeep ================================================ ================================================ FILE: client/web/addons/encoding/tests/unit/string-test.ts ================================================ import { module, test } from 'qunit'; import * as stringEncoding from '@emberclear/encoding/string'; module('Unit | Utility | String Encoding', function () { module('toHex / fromHex', function () { test('converts uint8array and back', function (assert) { const msgAsUint8 = Uint8Array.from([104, 101, 108, 108, 111]); // hello const hex = stringEncoding.toHex(msgAsUint8); const original = stringEncoding.fromHex(hex); assert.deepEqual(original, msgAsUint8); }); }); module('to/from base64 string / object', function () { test('converts to base64 and back', function (assert) { const obj = { hi: 'there' }; const base64 = stringEncoding.convertObjectToBase64String(obj); const original = stringEncoding.convertBase64StringToObject(base64); assert.deepEqual(original, obj); }); }); module('base64ToHex', function () { test('converts', function (assert) { const base64 = 'aGVsbG8gdGhlcmU='; // hello there const expected = '68656C6C6F207468657265'; const result = stringEncoding.base64ToHex(base64); assert.equal(result, expected); }); }); }); ================================================ FILE: client/web/addons/encoding/tsconfig.compiler-options.json ================================================ { // Alias to reduce the number of ../ in paths "extends": "../../config/tsconfig.compiler-options.json" } ================================================ FILE: client/web/addons/encoding/tsconfig.json ================================================ { "files": [], "compilerOptions": { "composite": true }, "references": [ { "path": "addon" }, /* { "path": "addon-test-support" }, */ { "path": "tests" } ] } ================================================ FILE: client/web/addons/encoding/vendor/.gitkeep ================================================ ================================================ FILE: client/web/addons/local-account/.editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: client/web/addons/local-account/.ember-cli ================================================ { /** Ember CLI sends analytics information by default. The data is completely anonymous, but there are times when you might want to disable this behavior. Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false } ================================================ FILE: client/web/addons/local-account/.eslintignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/local-account/.eslintrc.js ================================================ 'use strict'; const { configs } = require('@nullvoxpopuli/eslint-configs'); module.exports = configs.ember(); ================================================ FILE: client/web/addons/local-account/.gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/ /tmp/ # dependencies /bower_components/ /node_modules/ # misc /.env* /.pnp* /.sass-cache /connect.lock /coverage/ /libpeerconnection.log /npm-debug.log* /testem.log /yarn-error.log # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/local-account/.npmignore ================================================ # compiled output /dist/ /tmp/ # dependencies /bower_components/ # misc /.bowerrc /.editorconfig /.ember-cli /.env* /.eslintignore /.eslintrc.js /.git/ /.gitignore /.template-lintrc.js /.travis.yml /.watchmanconfig /bower.json /config/ember-try.js /CONTRIBUTING.md /ember-cli-build.js /testem.js /tests/ /yarn.lock .gitkeep # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/local-account/.template-lintrc.js ================================================ 'use strict'; module.exports = require('@emberclear/config/.template-lintrc'); ================================================ FILE: client/web/addons/local-account/.travis.yml ================================================ --- language: node_js node_js: # we recommend testing addons with the same minimum supported node version as Ember CLI # so that your addon works for all apps - "10" dist: xenial addons: chrome: stable cache: yarn: true env: global: # See https://git.io/vdao3 for details. - JOBS=1 branches: only: - master # npm version tags - /^v\d+\.\d+\.\d+/ jobs: fast_finish: true allow_failures: - env: EMBER_TRY_SCENARIO=ember-canary include: # runs linting and tests with current locked deps - stage: "Tests" name: "Tests" script: - yarn lint - yarn test:ember - stage: "Additional Tests" name: "Floating Dependencies" install: - yarn install --no-lockfile --non-interactive script: - yarn test:ember # we recommend new addons test the current and previous LTS # as well as latest stable release (bonus points to beta/canary) - env: EMBER_TRY_SCENARIO=ember-lts-3.16 - env: EMBER_TRY_SCENARIO=ember-lts-3.20 - env: EMBER_TRY_SCENARIO=ember-release - env: EMBER_TRY_SCENARIO=ember-beta - env: EMBER_TRY_SCENARIO=ember-canary - env: EMBER_TRY_SCENARIO=ember-default-with-jquery - env: EMBER_TRY_SCENARIO=ember-classic before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH=$HOME/.yarn/bin:$PATH script: - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO ================================================ FILE: client/web/addons/local-account/.watchmanconfig ================================================ { "ignore_dirs": ["tmp", "dist"] } ================================================ FILE: client/web/addons/local-account/CONTRIBUTING.md ================================================ # How To Contribute ## Installation * `git clone ` * `cd local-account` * `yarn install` ## Linting * `yarn lint:hbs` * `yarn lint:js` * `yarn lint:js --fix` ## Running tests * `ember test` – Runs the test suite on the current Ember version * `ember test --server` – Runs the test suite in "watch mode" * `ember try:each` – Runs the test suite against multiple Ember versions ## Running the dummy application * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). ================================================ FILE: client/web/addons/local-account/LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: client/web/addons/local-account/README.md ================================================ local-account ============================================================================== [Short description of the addon.] Compatibility ------------------------------------------------------------------------------ * Ember.js v3.16 or above * Ember CLI v2.13 or above * Node.js v10 or above Installation ------------------------------------------------------------------------------ ``` ember install local-account ``` Usage ------------------------------------------------------------------------------ [Longer description of how to use the addon in apps.] Contributing ------------------------------------------------------------------------------ See the [Contributing](CONTRIBUTING.md) guide for details. License ------------------------------------------------------------------------------ This project is licensed under the [MIT License](LICENSE.md). ================================================ FILE: client/web/addons/local-account/addon/adapters/application.js ================================================ /* eslint-disable */ // import LFAdapter from 'ember-localforage-adapter/adapters/localforage'; import EmberObject from '@ember/object'; import { reject, resolve, Promise as EmberPromise } from 'rsvp'; import Evented from '@ember/object/evented'; // import LFQueue from 'ember-localforage-adapter/utils/queue'; // import LFCache from 'ember-localforage-adapter/utils/cache'; import { v4 as uuid } from 'uuid'; import localforage from 'localforage'; const LFQueue = EmberObject.extend({ init: function () { this.queue = [resolve()]; }, attach(callback) { const queueKey = this.queue.length; this.queue[queueKey] = new EmberPromise((resolve, reject) => { this.queue[queueKey - 1].then(() => { this.queue.splice(queueKey - 1, 1); callback(resolve, reject); }); }); return this.queue[queueKey]; }, }); const LFCache = EmberObject.extend({ init: function () { this.data = new Map(); }, clear() { this.data.clear(); }, get(namespace) { const data = this.data.get(namespace); if (!data) { return null; } return data; }, set(namespace, objects) { this.data.set(namespace, objects); }, replace(data) { this.clear(); for (let index of Object.keys(data)) { this.set(index, data[index]); } }, }); const LFAdapter = EmberObject.extend(Evented, { queue: LFQueue.create(), cache: LFCache.create(), caching: 'model', coalesceFindRequests: true, groupRecordsForFindMany(store, snapshots) { return [snapshots]; }, shouldBackgroundReloadRecord() { return false; }, shouldReloadAll() { return true; }, /** * This is the main entry point into finding records. The first parameter to * this method is the model's name as a string. * * @method findRecord * @param store * @param {DS.Model} type * @param {Object|String|Integer|null} id */ findRecord(store, type, id) { return this._getNamespaceData(type).then((namespaceData) => { const record = namespaceData.records[id]; if (!record) { return reject(); } return record; }); }, findAll(store, type) { return this._getNamespaceData(type).then((namespaceData) => { const records = []; for (let id in namespaceData.records) { records.push(namespaceData.records[id]); } return { data: records.map((record) => record.data) }; }); }, findMany(store, type, ids) { return this._getNamespaceData(type).then((namespaceData) => { const records = []; for (let i = 0; i < ids.length; i++) { const record = namespaceData.records[ids[i]]; if (record) { records.push(record); } } return records; }); }, queryRecord(store, type, query) { return this._getNamespaceData(type).then((namespaceData) => { const record = this._query(namespaceData.records, query, true); if (!record) { return reject(); } return record; }); }, /** * Supports queries that look like this: * { * : , * ... * } * * Every property added to the query is an "AND" query, not "OR" * * Example: * match records with "complete: true" and the name "foo" or "bar" * { complete: true, name: /foo|bar/ } */ query(store, type, query) { return this._getNamespaceData(type).then((namespaceData) => { let records = this._query(namespaceData.records, query); let result = { data: records.map((record) => record.data) }; return result; }); }, _query(records, query, singleMatch) { const results = singleMatch ? null : []; for (let id in records) { const record = records[id]; const attributes = record.data.attributes; let isMatching = false; for (let property in query) { const queryValue = query[property]; if (queryValue instanceof RegExp) { isMatching = queryValue.test(attributes[property]); } else { isMatching = attributes[property] === queryValue; } if (!isMatching) { break; // all criteria should pass } } if (isMatching) { if (singleMatch) { return record; } results.push(record); } } return results; }, createRecord: updateOrCreate, updateRecord: updateOrCreate, deleteRecord(store, type, snapshot) { return this.queue.attach((resolve) => { this._getNamespaceData(type).then((namespaceData) => { delete namespaceData.records[snapshot.id]; this._setNamespaceData(type, namespaceData).then(() => { resolve(); }); }); }); }, generateIdForRecord() { return uuid(); }, // private _setNamespaceData(type, namespaceData) { const modelNamespace = this._modelNamespace(type); return this._loadData().then((storage) => { if (this.caching !== 'none') { this.cache.set(modelNamespace, namespaceData); } storage[modelNamespace] = namespaceData; return localforage.setItem(this._adapterNamespace(), storage); }); }, _getNamespaceData(type) { const modelNamespace = this._modelNamespace(type); if (this.caching !== 'none') { const cache = this.cache.get(modelNamespace); // eslint-disable-line if (cache) { return resolve(cache); } } return this._loadData().then((storage) => { const namespaceData = storage?.[modelNamespace] || { records: {} }; if (this.caching === 'model') { this.cache.set(modelNamespace, namespaceData); } else if (this.caching === 'all') { if (storage) { this.cache.replace(storage); } } return namespaceData; }); }, _loadData() { return localforage.getItem(this._adapterNamespace()).then((storage) => { return storage ? storage : {}; }); }, _modelNamespace(type) { return type.url || type.modelName; }, _adapterNamespace() { return this.namespace || 'DS.LFAdapter'; }, }); function updateOrCreate(store, type, snapshot) { return this.queue.attach((resolve) => { this._getNamespaceData(type).then((namespaceData) => { const serializer = store.serializerFor(type.modelName); const recordHash = serializer.serialize(snapshot, { includeId: true }); // update(id comes from snapshot) or create(id comes from serialization) const id = snapshot.id || recordHash.id; namespaceData.records[id] = recordHash; this._setNamespaceData(type, namespaceData).then(() => { resolve(); }); }); }); } export default LFAdapter.extend({ // do not change the namespace, as it would log everyone out // need a migration path if the namespace is going to change // namespace: 'emberclear', caching: 'none', shouldBackgroundReloadRecord() { return true; }, shouldBackgroundReloadAll() { return true; }, }); ================================================ FILE: client/web/addons/local-account/addon/index.ts ================================================ export { default as Channel } from './models/channel'; export { default as Contact, Status } from './models/contact'; export { default as Identity } from './models/identity'; export { default as User } from './models/user'; export { currentUserId, default as CurrentUserService } from './services/current-user'; ================================================ FILE: client/web/addons/local-account/addon/models/README.md ================================================ # TODO ## Channels The data structures needs to be reworked -- there are too many models and the split doesn't provide much value as all the data is very tightly coupled and it's a lot of 1:1 relationships For example, there are 7 relationships on identity _just_ for channels. Does it really need to exist on `Identity`? We don't super care what other users' channels are -- we can't actually know that for certain anyway. _Proposal_: - Move channel related things to the `User`. - Manage members on the `Channel`. - Manage votes on the `Channel`. ================================================ FILE: client/web/addons/local-account/addon/models/channel-context-chain.ts ================================================ import Model, { belongsTo, hasMany } from '@ember-data/model'; import type Channel from './channel'; import type Identity from './identity'; import type VoteChain from './vote-chain'; // TODO: CLEAN THIS UP // SEE README export default class ChannelContextChain extends Model { @belongsTo('identity', { async: false, inverse: 'adminOf' }) admin!: Identity; @hasMany('identity', { async: false, inverse: 'memberOf' }) members!: Identity[]; @belongsTo('vote-chain', { async: false }) supportingVote!: VoteChain; @belongsTo('channel-context-chain', { async: false, inverse: 'parentChain' }) previousChain!: ChannelContextChain; // Unused, but necessary to properly set up relationships, therefore async @belongsTo('channel-context-chain', { async: true, inverse: 'previousChain' }) parentChain!: ChannelContextChain; @belongsTo('channel', { async: true }) composesChannel!: Channel; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { 'channel-context-chain': ChannelContextChain; } } ================================================ FILE: client/web/addons/local-account/addon/models/channel.ts ================================================ import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import type ChannelContextChain from './channel-context-chain'; import type Vote from './vote'; // TODO: CLEAN THIS UP // SEE README export default class Channel extends Model { @attr() name!: string; // Optional super-private channel. // provides an additional layer of encryption // to protect from other people you trust, but // maybe don't trust *that* much. // TODO: implement this. // @attr() public protected!: boolean; // @attr() decryptionKey!: string; @hasMany('vote', { async: false }) activeVotes!: Vote[]; @belongsTo('channel-context-chain', { async: false }) contextChain!: ChannelContextChain; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { channel: Channel; } } ================================================ FILE: client/web/addons/local-account/addon/models/contact.ts ================================================ import { attr } from '@ember-data/model'; import Identity from './identity'; export const Status = { ONLINE: 'online', OFFLINE: 'offline', AWAY: 'away', BUSY: 'busy', } as const; type StatusKeys = keyof typeof Status; type STATUS = typeof Status[StatusKeys]; export default class Contact extends Identity { @attr() onlineStatus?: STATUS; @attr() isPinned?: boolean; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { contact: Contact; } } ================================================ FILE: client/web/addons/local-account/addon/models/identity.ts ================================================ import { tracked } from '@glimmer/tracking'; // see note below -- needs investigating // eslint-disable-next-line import { computed } from '@ember/object'; import Model, { attr, hasMany } from '@ember-data/model'; import { toHex } from '@emberclear/encoding/string'; import type ChannelContextChain from './channel-context-chain'; import type VoteChain from './vote-chain'; import type { KeyPublic, SigningKeyPublic } from '@emberclear/crypto'; export default class Identity extends Model implements Partial { @attr() name!: string; @attr() publicKey!: Uint8Array; @attr() publicSigningKey!: Uint8Array; // non-persisted data @tracked numUnread = 0; // human-readable data get publicKeyAsHex() { return toHex(this.publicKey); } get publicSigningKeyAsHex() { return toHex(this.publicSigningKey); } // Needed otherwise this regularly invalidates // TODO: will the public key ever change? who knows // eslint-disable-next-line @computed() get uid() { return this.publicKeyAsHex; } get displayName() { const name = this.name; const shortKey = this.publicKeyAsHex.substring(0, 8); return `${name} (${shortKey})`; } // TODO: CLEAN THIS UP // SEE README // Unused, but necessary to properly set up relationships, therefore async // eslint-disable-next-line prettier/prettier @hasMany('channel-context-chain', { async: true, inverse: 'admin' }) adminOf?: ChannelContextChain; // eslint-disable-next-line prettier/prettier @hasMany('channel-context-chain', { async: true, inverse: 'members' }) memberOf?: ChannelContextChain; @hasMany('vote-chain', { async: true, inverse: 'target' }) targetOfVote?: VoteChain; @hasMany('vote-chain', { async: true, inverse: 'key' }) voterOf?: VoteChain; @hasMany('vote-chain', { async: true, inverse: 'yes' }) votedYesIn?: VoteChain; @hasMany('vote-chain', { async: true, inverse: 'no' }) votedNoIn?: VoteChain; @hasMany('vote-chain', { async: true, inverse: 'remaining' }) stillRemainingIn?: VoteChain; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { identity: Identity; } } ================================================ FILE: client/web/addons/local-account/addon/models/user.ts ================================================ import { attr } from '@ember-data/model'; import Identity from './identity'; import type { KeyPair, SigningKeyPair } from '@emberclear/crypto'; export default class User extends Identity implements Partial, Partial { @attr() privateKey!: Uint8Array; @attr() privateSigningKey!: Uint8Array; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { user: User; } } ================================================ FILE: client/web/addons/local-account/addon/models/vote-chain.ts ================================================ import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import type ChannelContextChain from './channel-context-chain'; import type Identity from './identity'; import type Vote from './vote'; export enum VOTE_ACTION { ADD = 'add', REMOVE = 'remove', PROMOTE = 'promote', } // TODO: CLEAN THIS UP // SEE README export default class VoteChain extends Model { @hasMany('identity', { async: false, inverse: 'stillRemainingIn' }) remaining!: Identity[]; @hasMany('identity', { async: false, inverse: 'votedYesIn' }) yes!: Identity[]; @hasMany('identity', { async: false, inverse: 'votedNoIn' }) no!: Identity[]; @belongsTo('identity', { async: false, inverse: 'targetOfVote' }) target!: Identity; @attr() action!: VOTE_ACTION; @belongsTo('identity', { async: false, inverse: 'voterOf' }) key!: Identity; @belongsTo('vote-chain', { async: false, inverse: 'parentChain' }) previousVoteChain!: VoteChain; @attr() signature!: Uint8Array; // Unused, but necessary to properly set up relationships, therefore async @belongsTo('vote-chain', { async: true, inverse: 'previousVoteChain' }) parentChain!: VoteChain; @belongsTo('channel-context-chain', { async: true }) supports!: ChannelContextChain; @belongsTo('vote', { async: true }) wrappedIn!: Vote; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { 'vote-chain': VoteChain; } } ================================================ FILE: client/web/addons/local-account/addon/models/vote.ts ================================================ import Model, { belongsTo } from '@ember-data/model'; import type Channel from './channel'; import type VoteChain from './vote-chain'; // TODO: CLEAN THIS UP // SEE README export default class Vote extends Model { @belongsTo('vote-chain', { async: false }) voteChain!: VoteChain; // Unused, but necessary to properly set up relationships, therefore async @belongsTo('channel', { async: true }) activeVoteIn!: Channel; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { vote: Vote; } } ================================================ FILE: client/web/addons/local-account/addon/serializers/application.js ================================================ const myFancyInflector = { messages: 'message', channels: 'channel', contacts: 'contact', identities: 'identity', 'invitation-results': 'invitations-result', invitations: 'invitation', 'message-medias': 'message-media', relays: 'relay', users: 'user', }; function enforceSingularTypesInDocument(document) { if (Array.isArray(document.data)) { document.data.map(singularizeResource); } else if (document.data) { singularizeResource(document.data); } if (Array.isArray(document.included)) { document.included.map(singularizeResource); } return document; } function singularizeResource(resource) { resource.type = singularize(resource.type); if (resource.relationships) { Object.keys(resource.relationships).forEach((key) => { let data = resource.relationships[key].data; if (Array.isArray(data)) { data.forEach((r) => { r.type = singularize(r.type); }); } else if (data) { data.type = singularize(data.type); } }); } } function singularize(str) { return myFancyInflector[str] || str; } export default class ApplicationSerializer { static create() { return new ApplicationSerializer(); } normalizeResponse(_store, _schema, jsonApiDocument) { let normalized = enforceSingularTypesInDocument(jsonApiDocument); return normalized; } serialize(snapshot /*, options */) { let type = snapshot.modelName; let id = snapshot.id; let attributes = snapshot.attributes(); let relationships = {}; snapshot.eachRelationship((key, meta) => { let r = (relationships[key] = {}); if (meta.kind === 'belongsTo') { let related = snapshot.belongsTo(key); if (!related) { r.data = null; } else { if (!related.id) { throw new Error(`Attempting to save a relationship that has no id`); } r.data = { type: related.modelName, id: related.id }; } } else { let relatedSnapshots = snapshot.hasMany(key); r.data = []; if (!relatedSnapshots) { return; } relatedSnapshots.map((related) => { if (!related.id) { throw new Error(`Attempting to save a relationship that has no id`); } r.data.push({ type: related.modelName, id: related.id }); }); } }); return { data: { type, id, attributes, relationships, }, }; } } ================================================ FILE: client/web/addons/local-account/addon/services/channel-manager.ts ================================================ import Service from '@ember/service'; import { inject as service } from '@ember/service'; import type ArrayProxy from '@ember/array/proxy'; import type StoreService from '@ember-data/store'; import type { Channel } from '@emberclear/local-account'; export default class ChannelManager extends Service { @service declare store: StoreService; async findOrCreate(id: string, name: string): Promise { try { return await this.findAndSetName(id, name); } catch (e) { return await this.create(id, name); } } async findAndSetName(id: string, name: string): Promise { let record = await this.find(id); record.name = name; await record.save(); return record; } async create(id: string, name: string): Promise { let record = this.store.createRecord('channel', { id, name }); await record.save(); return record; } async allChannels(): Promise> { const channels = await this.store.findAll('channel'); return channels; } async find(uid: string) { return await this.store.findRecord('channel', uid); } } ================================================ FILE: client/web/addons/local-account/addon/services/contact-manager.ts ================================================ import { tracked } from '@glimmer/tracking'; import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { fromHex } from '@emberclear/encoding/string'; import type ArrayProxy from '@ember/array/proxy'; import type StoreService from '@ember-data/store'; import type { Contact } from '@emberclear/local-account'; import type { ImportableIdentity } from '@emberclear/local-account/types'; export default class ContactManager extends Service { @service declare store: StoreService; @tracked isImporting = false; find(uid: string) { return this.store.findRecord('contact', uid); } async import(contacts: Partial[]) { this.isImporting = true; try { for await (let contact of contacts) { if (!contact.publicKey || !contact.name) return Promise.resolve(); await this.findOrCreate(contact.publicKey, contact.name); } } finally { this.isImporting = false; } } async findOrCreate(uid: string, name: string): Promise { try { // an exception thrown here is never caught return await this.findAndSetName(uid, name); } catch (e) { return await this.create(uid, name); } } async allContacts(): Promise> { const contacts = await this.store.findAll('contact'); return contacts; } async create(uid: string, name: string): Promise { const publicKey = fromHex(uid); let record = this.store.createRecord('contact', { id: uid, publicKey, name, }); await record.save(); return record; } async addContact(/* _info: any */) { try { // const existing = this.find(info.id); // return? error? } catch (e) { // maybe find should do the try/catching... // seems weird to try/catch every time we want to use find } } private async findAndSetName(uid: string, name: string): Promise { let record = await this.find(uid); // always update the name record.name = name; await record.save(); return record; } } declare module '@ember/service' { interface Registry { 'contact-manager': ContactManager; } } ================================================ FILE: client/web/addons/local-account/addon/services/current-user.ts ================================================ import { tracked } from '@glimmer/tracking'; import { assert } from '@ember/debug'; import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { isPresent } from '@ember/utils'; import { timeout } from 'ember-concurrency'; import { dropTask } from 'ember-concurrency-decorators'; import { taskFor } from 'ember-concurrency-ts'; import { CryptoConnector } from '@emberclear/crypto'; import { toHex } from '@emberclear/encoding/string'; import type StoreService from '@ember-data/store'; import type { KeyPair, SigningKeyPair, WorkersService } from '@emberclear/crypto'; import type User from '@emberclear/local-account/models/user'; export const currentUserId = 'me'; // The purpose of this service is to be an interface that // handles syncing between the data store and persistent localstorage. // // if the identity doesn't already exist in the store, it will try // try to be loaded from localstorage. // if the identity does exist in in teh store, localstorage will // overwrite what is in the store. // the only time the localstorage copy of the identity is written to // is upon update and initial creation of the identity data. export default class CurrentUserService extends Service { @service declare workers: WorkersService; @service declare store: StoreService; declare __crypto__?: CryptoConnector; @tracked declare __record__?: User; get record() { assert(`current-user.record cannot be accessed until the record is loaded`, this.__record__); return this.__record__; } get crypto() { assert( `current-user.crypto cannot be accessed without loading the crypto web worker`, this.__crypto__ ); return this.__crypto__; } get id() { if (!this.__record__) return; return this.__record__.id; } get name() { if (!this.__record__) return; return this.__record__.name; } get publicKey() { if (!this.__record__) return; return this.record.publicKey; } get privateKey() { if (!this.__record__) return; return this.__record__.privateKey; } get publicSigningKey() { if (!this.__record__) return; return this.__record__.publicSigningKey; } get privateSigningKey() { if (!this.__record__) return; return this.__record__.privateSigningKey; } get isLoggedIn(): boolean { if (!this.__record__) { return false; } return !!(this.privateKey && this.publicKey); } get uid(): string { if (!this.publicKey) return ''; return toHex(this.publicKey); } get shareUrl(): string { const uri = `${window.location.origin}/invite?name=${this.name}&publicKey=${this.uid}`; return encodeURI(uri); } async create(name: string): Promise { await this.hydrateCrypto(); assert(`Expected crypto to be setup`, this.__crypto__); const { publicKey, privateKey } = await this.__crypto__.generateKeys(); const { publicSigningKey, privateSigningKey } = await this.__crypto__.generateSigningKeys(); // remove existing record await this.store.unloadAll('user'); await this.setIdentity(name, { privateKey, publicKey, privateSigningKey, publicSigningKey }); await this.load(); } async setIdentity(name: string, keys: KeyPair & Partial) { const record = this.store.createRecord('user', { id: currentUserId, name, ...keys, }); await record.save(); this.hydrateCrypto(record); this.__record__ = record; } async exists(): Promise { let identity = await this.currentUser(); if (!identity) return false; return isPresent(identity.privateKey); } async load(): Promise { try { const existing = await this.store.findRecord('user', currentUserId, { backgroundReload: true, }); await this.hydrateCrypto(existing); this.__record__ = existing; // eslint-disable-next-line @typescript-eslint/no-floating-promises taskFor(this.migrate).perform(); return existing; } catch (e) { // When the user doesn't exist, e is undefined??? // why? // TODO: when implementing custom indexeddb adapter.. // don't throw undefined? lol if (e) { throw e; } } return null; } async currentUser(): Promise { if (!this.__record__) return await this.load(); return this.__record__; } @dropTask async migrate() { // This is a super HACK :( // for some reason, I can't find and update a record in the same // async function... why? await timeout(1000); if (!this.__record__ || !this.__crypto__) { return; } if (!this.privateSigningKey) { let { publicSigningKey, privateSigningKey } = await this.__crypto__.generateSigningKeys(); this.__record__.setProperties({ publicSigningKey, privateSigningKey, }); await this.__record__?.save(); } } hydrateCrypto(user?: KeyPair) { if (this.__crypto__) { if (user) { this.__crypto__.keys = user; } return; } this.__crypto__ = new CryptoConnector({ workerService: this.workers, keys: user, }); } async importFromKey(name: string, privateKey: Uint8Array, privateSigningKey?: Uint8Array) { this.hydrateCrypto(); assert(`Expected crypto to be setup`, this.__crypto__); const publicKey = await this.__crypto__.derivePublicKey(privateKey); let publicSigningKey; if (privateSigningKey) { publicSigningKey = await this.__crypto__.derivePublicSigningKey(privateSigningKey); } else { let signingKeys = await this.__crypto__.generateSigningKeys(); publicSigningKey = signingKeys.publicSigningKey; privateSigningKey = signingKeys.privateSigningKey; } await this.setIdentity(name, { privateKey, publicKey, privateSigningKey, publicSigningKey }); } } // DO NOT DELETE: this is how TypeScript knows how to look up your services. declare module '@ember/service' { interface Registry { 'current-user': CurrentUserService; } } ================================================ FILE: client/web/addons/local-account/addon/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations", "paths": { "@emberclear/local-account": ["."], "@emberclear/local-account/*": ["./*"], "*": ["../types/*"] } }, "include": [".", "../types"], "references": [ { "path": "../../../libraries/questionably-typed" }, { "path": "../../crypto" }, { "path": "../../encoding" } ] } ================================================ FILE: client/web/addons/local-account/addon/types.ts ================================================ export interface ImportableIdentity { name: string; publicKey: string; } ================================================ FILE: client/web/addons/local-account/addon/utils.ts ================================================ import type Contact from './models/contact'; export function isContact(maybe: unknown): maybe is Contact { if (typeof maybe !== 'object') return false; if (!maybe) return false; return 'isPinned' in maybe; } ================================================ FILE: client/web/addons/local-account/addon-test-support/-private/contact.ts ================================================ import { generateAsymmetricKeys } from '@emberclear/crypto/workers/crypto/utils/nacl'; import { toHex } from '@emberclear/encoding/string'; import { Status } from '@emberclear/local-account'; import { getService } from '@emberclear/test-helpers/test-support'; import { createRecord } from './utils'; import type { Contact } from '@emberclear/local-account'; export async function attributesForContact() { const { publicKey } = await generateAsymmetricKeys(); const id = toHex(publicKey); return { id, publicKey }; } export async function buildContact(name: string, attributes = {}): Promise { const store = getService('store'); const defaultAttributes = await attributesForContact(); const record = createRecord(store, 'contact', { name, onlineStatus: Status.OFFLINE, ...defaultAttributes, ...attributes, }); return record; } export async function createContact(name: string, attributes = {}): Promise { const record = await buildContact(name, attributes); await record.save(); return record; } ================================================ FILE: client/web/addons/local-account/addon-test-support/-private/current-user.ts ================================================ import { settled } from '@ember/test-helpers'; import { generateAsymmetricKeys } from '@emberclear/crypto/workers/crypto/utils/nacl'; import { getService, getStore } from '@emberclear/test-helpers/test-support'; import type { User } from '@emberclear/local-account'; export async function createCurrentUser(): Promise { const store = getStore(); const currentUserService = getService('current-user'); const { publicKey, privateKey } = await generateAsymmetricKeys(); const record = store.createRecord('user', { id: 'me', name: 'Test User', publicKey, privateKey, }); await record.save(); currentUserService.__record__ = record; currentUserService.hydrateCrypto({ publicKey, privateKey }); await settled(); return record; } export function setupCurrentUser(hooks: NestedHooks) { hooks.beforeEach(async function () { await createCurrentUser(); }); } export function getCurrentUser() { return getService('current-user').record; } ================================================ FILE: client/web/addons/local-account/addon-test-support/-private/storage.ts ================================================ import localforage from 'localforage'; import type Ember from 'ember'; import type { TestContext } from 'ember-test-helpers'; async function cleanEverything(owner: Ember.ApplicationInstance) { const adapter = owner.lookup('adapter:application'); await adapter.cache.clear(); // specifically, offline storage await localforage.clear(); localStorage.clear(); } export function clearLocalStorage(hooks: NestedHooks) { hooks.beforeEach(async function (this: TestContext) { await cleanEverything(this.owner); }); hooks.afterEach(async function (this: TestContext) { await cleanEverything(this.owner); }); } ================================================ FILE: client/web/addons/local-account/addon-test-support/-private/user.ts ================================================ import { generateAsymmetricKeys, generateSigningKeys, } from '@emberclear/crypto/workers/crypto/utils/nacl'; import { toHex } from '@emberclear/encoding/string'; import { getService } from '@emberclear/test-helpers/test-support'; import { createRecord } from './utils'; import type { User } from '@emberclear/local-account'; export async function attributesForUser() { const { publicKey } = await generateAsymmetricKeys(); const { publicSigningKey, privateSigningKey } = await generateSigningKeys(); const id = toHex(publicKey); return { id, publicKey, publicSigningKey, privateSigningKey }; } export async function buildUser(name: string, attributes = {}): Promise { const store = getService('store'); const defaultAttributes = await attributesForUser(); const record = createRecord(store, 'user', { name, ...defaultAttributes, ...attributes, }); return record; } export async function createUser(name: string, attributes = {}): Promise { const record = await buildUser(name, attributes); await record.save(); return record; } ================================================ FILE: client/web/addons/local-account/addon-test-support/-private/utils.ts ================================================ import type StoreService from '@ember-data/store'; import type { Contact, Identity, User } from '@emberclear/local-account'; interface ModelRegistry { contact: Contact; user: User; identity: Identity; } /** * Helper that doesn't use the same type registry as ember-data. * * the regular store.createRecord uses ember-data/types/registries/model * which we can't use because we allow apps to create their own definitions * of these models. * */ export function createRecord( store: StoreService, modelName: Key, attributes: Record ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return store.createRecord(modelName as any, attributes) as ModelRegistry[Key]; } ================================================ FILE: client/web/addons/local-account/addon-test-support/index.ts ================================================ export { attributesForContact, buildContact, createContact } from './-private/contact'; export { createCurrentUser, getCurrentUser, setupCurrentUser } from './-private/current-user'; export { clearLocalStorage } from './-private/storage'; export { attributesForUser, buildUser, createUser } from './-private/user'; ================================================ FILE: client/web/addons/local-account/addon-test-support/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations/test-support", "paths": { "@emberclear/local-account": ["../declarations"], "@emberclear/local-account/*": ["../declarations/*"], "@emberclear/local-account/test-support": ["."], "@emberclear/local-account/test-support/*": ["./*"] } }, "references": [{ "path": "../addon" }, { "path": "../../test-helpers" }] } ================================================ FILE: client/web/addons/local-account/app/adapters/application.js ================================================ export { default } from '@emberclear/local-account/adapters/application'; ================================================ FILE: client/web/addons/local-account/app/models/channel-context-chain.js ================================================ export { default } from '@emberclear/local-account/models/channel-context-chain'; ================================================ FILE: client/web/addons/local-account/app/models/channel.js ================================================ export { default } from '@emberclear/local-account/models/channel'; ================================================ FILE: client/web/addons/local-account/app/models/contact.js ================================================ export { default } from '@emberclear/local-account/models/contact'; ================================================ FILE: client/web/addons/local-account/app/models/identity.js ================================================ export { default } from '@emberclear/local-account/models/identity'; ================================================ FILE: client/web/addons/local-account/app/models/user.js ================================================ export { default } from '@emberclear/local-account/models/user'; ================================================ FILE: client/web/addons/local-account/app/models/vote-chain.js ================================================ export { default } from '@emberclear/local-account/models/vote-chain'; ================================================ FILE: client/web/addons/local-account/app/models/vote.js ================================================ export { default } from '@emberclear/local-account/models/vote'; ================================================ FILE: client/web/addons/local-account/app/serializers/application.js ================================================ export { default } from '@emberclear/local-account/serializers/application'; ================================================ FILE: client/web/addons/local-account/app/services/channel-manager.js ================================================ export { default } from '@emberclear/local-account/services/channel-manager'; ================================================ FILE: client/web/addons/local-account/app/services/contact-manager.js ================================================ export { default } from '@emberclear/local-account/services/contact-manager'; ================================================ FILE: client/web/addons/local-account/app/services/current-user.js ================================================ export { default } from '@emberclear/local-account/services/current-user'; ================================================ FILE: client/web/addons/local-account/app/services/store.js ================================================ import { RecordData } from '@ember-data/record-data/-private'; import { default as Store } from '@ember-data/store'; import { identifierCacheFor } from '@ember-data/store/-private'; export default class StoreService extends Store { createRecordDataFor(modelName, id, clientId, storeWrapper) { let identifier = identifierCacheFor(this).getOrCreateRecordIdentifier({ type: modelName, id, lid: clientId, }); return new RecordData(identifier, storeWrapper); } } ================================================ FILE: client/web/addons/local-account/config/ember-try.js ================================================ 'use strict'; const getChannelURL = require('ember-source-channel-url'); module.exports = async function () { return { useYarn: true, scenarios: [ { name: 'ember-lts-3.16', npm: { devDependencies: { 'ember-source': '~3.16.0', }, }, }, { name: 'ember-lts-3.20', npm: { devDependencies: { 'ember-source': '~3.20.5', }, }, }, { name: 'ember-release', npm: { devDependencies: { 'ember-source': await getChannelURL('release'), }, }, }, { name: 'ember-beta', npm: { devDependencies: { 'ember-source': await getChannelURL('beta'), }, }, }, { name: 'ember-canary', npm: { devDependencies: { 'ember-source': await getChannelURL('canary'), }, }, }, { name: 'ember-default-with-jquery', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true, }), }, npm: { devDependencies: { '@ember/jquery': '^1.1.0', }, }, }, { name: 'ember-classic', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'application-template-wrapper': true, 'default-async-observers': false, 'template-only-glimmer-components': false, }), }, npm: { ember: { edition: 'classic', }, }, }, ], }; }; ================================================ FILE: client/web/addons/local-account/config/environment.js ================================================ 'use strict'; module.exports = function (/* environment, appConfig */) { return {}; }; ================================================ FILE: client/web/addons/local-account/ember-cli-build.js ================================================ 'use strict'; const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function (defaults) { let app = new EmberAddon(defaults, { // Add options here 'ember-cli-babel': { enableTypeScriptTransform: true, }, }); /* This build file specifies the options for the dummy test app of this addon, located in `/tests/dummy` This build file does *not* influence how the addon or the app using it behave. You most likely want to be modifying `./index.js` or app's build file */ return app.toTree(); }; ================================================ FILE: client/web/addons/local-account/index.js ================================================ 'use strict'; module.exports = { name: require('./package').name, options: { 'ember-cli-babel': { enableTypeScriptTransform: true, }, }, }; ================================================ FILE: client/web/addons/local-account/package.json ================================================ { "name": "@emberclear/local-account", "version": "0.0.0", "description": "Implements client-side account management", "keywords": [ "ember-addon" ], "repository": { "url": "https://github.com/NullVoxPopuli/emberclear", "directory": "client/web/addons/local-account" }, "license": "GPL-3.0", "author": "NullVoxPopuli", "directories": { "doc": "doc", "test": "tests" }, "scripts": { "build": "ember build --environment=production", "lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*", "lint:hbs": "ember-template-lint .", "lint:js": "eslint .", "start": "ember serve", "test": "ember test", "test:try-one": "ember try:one", "test:ember-compatibility": "ember try:each" }, "dependencies": { "@ember-data/debug": "3.26.0", "@ember-data/model": "3.26.0", "@ember-data/record-data": "3.26.0", "@ember-data/store": "3.26.0", "@emberclear/crypto": "*", "@emberclear/encoding": "*", "ember-auto-import": "1.11.3", "ember-cli-babel": "7.26.6", "ember-cli-htmlbars": "5.7.1", "ember-concurrency": "1.3.0", "ember-concurrency-async": "0.3.2", "ember-concurrency-decorators": "2.0.3", "ember-concurrency-test-waiter": "0.4.0", "ember-concurrency-ts": "0.2.2", "ember-inflector": "^4.0.2", "localforage": "1.9.0", "uuid": "^8.3.2" }, "devDependencies": { "@ember/optional-features": "^2.0.0", "@emberclear/config": "*", "@emberclear/questionably-typed": "*", "@emberclear/test-helpers": "*", "@glimmer/component": "1.0.4", "@glimmer/tracking": "1.0.4", "@nullvoxpopuli/eslint-configs": "1.3.2", "@types/ember": "3.16.5", "@types/ember-data": "3.16.14", "@types/ember-data__model": "3.16.2", "@types/ember-data__store": "3.16.1", "@types/ember-qunit": "3.4.13", "@types/ember-resolver": "^5.0.10", "@types/ember__test-helpers": "2.0.0", "@types/qunit": "2.11.1", "@types/rsvp": "4.0.3", "broccoli-asset-rev": "^3.0.0", "ember-cli": "3.26.1", "ember-cli-dependency-checker": "^3.2.0", "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "4.0.2", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", "ember-load-initializers": "2.1.2", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", "ember-resolver": "^8.0.2", "ember-source": "3.26.1", "ember-source-channel-url": "^3.0.0", "ember-try": "^1.4.0", "loader.js": "^4.7.0", "npm-run-all": "^4.1.5", "qunit-assertions-extra": "0.8.5", "qunit-dom": "1.6.0", "typescript": "4.3.4" }, "engines": { "node": "14.17.1" }, "typesVersions": { "*": { "*": [ "declarations/*", "declarations/*/index" ] } }, "volta": { "node": "14.17.1", "yarn": "1.22.10" }, "ember": { "edition": "octane" }, "ember-addon": { "configPath": "tests/dummy/config" } } ================================================ FILE: client/web/addons/local-account/testem.js ================================================ 'use strict'; module.exports = { test_page: 'tests/index.html?hidepassed', disable_watching: true, launch_in_ci: ['Chrome'], launch_in_dev: ['Chrome'], browser_start_timeout: 120, browser_args: { Chrome: { ci: [ // --no-sandbox is needed when running Chrome inside a container process.env.CI ? '--no-sandbox' : null, '--headless', '--disable-dev-shm-usage', '--disable-software-rasterizer', '--mute-audio', '--remote-debugging-port=0', '--window-size=1440,900', ].filter(Boolean), }, }, }; ================================================ FILE: client/web/addons/local-account/tests/dummy/app/app.ts ================================================ import Application from '@ember/application'; import config from 'dummy/config/environment'; import loadInitializers from 'ember-load-initializers'; import Resolver from 'ember-resolver'; export default class App extends Application { modulePrefix = config.modulePrefix; podModulePrefix = config.podModulePrefix; Resolver = Resolver; } loadInitializers(App, config.modulePrefix); ================================================ FILE: client/web/addons/local-account/tests/dummy/app/components/.gitkeep ================================================ ================================================ FILE: client/web/addons/local-account/tests/dummy/app/config/environment.d.ts ================================================ export default config; /** * Type declarations for * import config from './config/environment' * * For now these need to be managed by the developer * since different ember addons can materialize new entries. */ declare const config: { environment: 'production' | 'test' | 'development'; modulePrefix: string; podModulePrefix: string; locationType: string; rootURL: string; host: string; APP: Record; }; ================================================ FILE: client/web/addons/local-account/tests/dummy/app/controllers/.gitkeep ================================================ ================================================ FILE: client/web/addons/local-account/tests/dummy/app/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/local-account/tests/dummy/app/index.html ================================================ Dummy {{content-for "head"}} {{content-for "head-footer"}} {{content-for "body"}} {{content-for "body-footer"}} ================================================ FILE: client/web/addons/local-account/tests/dummy/app/models/.gitkeep ================================================ ================================================ FILE: client/web/addons/local-account/tests/dummy/app/router.js ================================================ import EmberRouter from '@ember/routing/router'; import config from 'dummy/config/environment'; export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; } Router.map(function () {}); ================================================ FILE: client/web/addons/local-account/tests/dummy/app/routes/.gitkeep ================================================ ================================================ FILE: client/web/addons/local-account/tests/dummy/app/styles/app.css ================================================ ================================================ FILE: client/web/addons/local-account/tests/dummy/app/templates/application.hbs ================================================ {{outlet}} ================================================ FILE: client/web/addons/local-account/tests/dummy/config/ember-cli-update.json ================================================ { "schemaVersion": "1.0.0", "packages": [ { "name": "ember-cli", "version": "3.23.0", "blueprints": [ { "name": "addon", "outputRepo": "https://github.com/ember-cli/ember-addon-output", "codemodsSource": "ember-addon-codemods-manifest@1", "isBaseBlueprint": true, "options": [ "--yarn" ] } ] } ] } ================================================ FILE: client/web/addons/local-account/tests/dummy/config/environment.js ================================================ 'use strict'; module.exports = function (environment) { let ENV = { modulePrefix: 'dummy', environment, rootURL: '/', locationType: 'auto', EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true }, EXTEND_PROTOTYPES: { // Prevent Ember Data from overriding Date.parse. Date: false, }, }, APP: { // Here you can pass flags/options to your application instance // when it is created }, }; if (environment === 'development') { // ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_ACTIVE_GENERATION = true; // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; } if (environment === 'test') { // Testem prefers this... ENV.locationType = 'none'; // keep test console output quieter ENV.APP.LOG_ACTIVE_GENERATION = false; ENV.APP.LOG_VIEW_LOOKUPS = false; ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; } if (environment === 'production') { // here you can enable a production-specific feature } return ENV; }; ================================================ FILE: client/web/addons/local-account/tests/dummy/config/optional-features.json ================================================ { "application-template-wrapper": false, "default-async-observers": true, "jquery-integration": false, "template-only-glimmer-components": true } ================================================ FILE: client/web/addons/local-account/tests/dummy/config/targets.js ================================================ 'use strict'; const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; const isCI = Boolean(process.env.CI); const isProduction = process.env.EMBER_ENV === 'production'; if (isCI || isProduction) { browsers.push('ie 11'); } module.exports = { browsers, }; ================================================ FILE: client/web/addons/local-account/tests/dummy/public/robots.txt ================================================ # http://www.robotstxt.org User-agent: * Disallow: ================================================ FILE: client/web/addons/local-account/tests/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/local-account/tests/index.html ================================================ Dummy Tests {{content-for "head"}} {{content-for "test-head"}} {{content-for "head-footer"}} {{content-for "test-head-footer"}} {{content-for "body"}} {{content-for "test-body"}} {{content-for "body-footer"}} {{content-for "test-body-footer"}} ================================================ FILE: client/web/addons/local-account/tests/integration/.gitkeep ================================================ ================================================ FILE: client/web/addons/local-account/tests/test-helper.ts ================================================ // Install Types and assertion extensions import 'qunit-dom'; import 'qunit-assertions-extra'; import { setApplication } from '@ember/test-helpers'; import { start } from 'ember-qunit'; import Application from 'dummy/app'; import config from 'dummy/config/environment'; setApplication(Application.create(config.APP)); start(); ================================================ FILE: client/web/addons/local-account/tests/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", // Question: what's the best place for test and dummy declarations to go? They // aren't actually needed for anything other than to satisfy the requirements // for a composite build. "declarationDir": "./dummy/declarations", "paths": { "dummy/tests/*": ["./*"], "dummy/*": ["./dummy/app/*"], "@emberclear/local-account": ["../declarations"], "@emberclear/local-account/*": ["../declarations/*"], "*": ["../types/*"] } }, "include": [ ".", "../types" ], "references": [ { "path": "../addon" }, { "path": "../addon-test-support" } ] } ================================================ FILE: client/web/addons/local-account/tests/unit/create-current-user-test.ts ================================================ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { clearLocalStorage, createCurrentUser, setupCurrentUser, } from '@emberclear/local-account/test-support'; import { getService, getStore } from '@emberclear/test-helpers/test-support'; module('TestHelper | create-current-user', function (hooks) { setupApplicationTest(hooks); clearLocalStorage(hooks); test('a new user is created and kept in cache', async function (assert) { const before = getStore().peekAll('user'); assert.equal(before.length, 0); await createCurrentUser(); const after = getStore().peekAll('user'); assert.equal(after.length, 1); assert.equal(after.toArray()[0].id, 'me'); }); test('a new user is created and stored', async function (assert) { const before = await getStore().findAll('user'); assert.equal(before.length, 0); await createCurrentUser(); const after = await getStore().findAll('user'); assert.equal(after.length, 1); assert.equal(after.toArray()[0].id, 'me'); }); test('the user is set on the identity service', async function (assert) { const before = getService('current-user').__record__; assert.notOk(before); const user = await createCurrentUser(); const after = getService('current-user').__record__; assert.deepEqual(after, user); }); module('user is setup in a beforeEach', function (hooks) { setupCurrentUser(hooks); test('the user is logged in', function (assert) { const isLoggedIn = getService('current-user').isLoggedIn; assert.ok(isLoggedIn); }); test('identity exists', async function (assert) { const exists = await getService('current-user').exists(); assert.ok(exists); }); }); }); ================================================ FILE: client/web/addons/local-account/tests/unit/models/contact-test.ts ================================================ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { buildContact } from '@emberclear/local-account/test-support'; import { getStore } from '@emberclear/test-helpers/test-support'; module('Unit | Model | contact', function (hooks) { setupTest(hooks); // Replace this with your real tests. test('it exists', function (assert) { let model = getStore().createRecord('contact', {}); assert.ok(model); }); module('displayName', function () { test('is derived from name and public key', async function (assert) { let contact = await buildContact('NullVoxPopuli'); assert.matches(contact.displayName, /^NullVoxPopuli \([0-9abcdef]{8}\)/); }); }); }); ================================================ FILE: client/web/addons/local-account/tests/unit/models/user-test.ts ================================================ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { getStore } from '@emberclear/test-helpers/test-support'; module('Unit | Model | user', function (hooks) { setupTest(hooks); // Replace this with your real tests. test('it exists', function (assert) { let model = getStore().createRecord('user', {}); assert.ok(model); }); }); ================================================ FILE: client/web/addons/local-account/tests/unit/services/current-user-test.ts ================================================ import { module, skip } from 'qunit'; import { setupTest, test } from 'ember-qunit'; import { generateAsymmetricKeys } from '@emberclear/crypto/workers/crypto/utils/nacl'; import { getService } from '@emberclear/test-helpers/test-support'; import type { CurrentUserService } from '@emberclear/local-account'; module('Unit | Service | identity', function (hooks) { setupTest(hooks); let service: CurrentUserService; hooks.beforeEach(() => { service = getService('current-user'); }); test('importFromKey where privateSigningKey is not present generates signing keys', async function (assert) { let keys = await generateAsymmetricKeys(); await service.importFromKey('name', keys.privateKey); assert.ok(service.record); assert.ok(service.record.publicSigningKey); assert.ok(service.record.privateSigningKey); }); skip('can dump and reload', async function (assert) { assert.expect(0); }); }); ================================================ FILE: client/web/addons/local-account/tsconfig.compiler-options.json ================================================ { // Alias to reduce the number of ../ in paths "extends": "../../config/tsconfig.compiler-options.json" } ================================================ FILE: client/web/addons/local-account/tsconfig.json ================================================ { "files": [], "compilerOptions": { "composite": true }, "references": [ { "path": "addon" }, { "path": "addon-test-support" }, { "path": "tests" } ] } ================================================ FILE: client/web/addons/local-account/types/overrides.d.ts ================================================ import '@emberclear/questionably-typed/overrides'; import 'ember-concurrency-decorators'; import 'ember-concurrency-async'; import 'ember-concurrency-ts/async'; import 'ember-concurrency-test-waiter'; ================================================ FILE: client/web/addons/local-account/vendor/.gitkeep ================================================ ================================================ FILE: client/web/addons/networking/.editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: client/web/addons/networking/.ember-cli ================================================ { /** Ember CLI sends analytics information by default. The data is completely anonymous, but there are times when you might want to disable this behavior. Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false } ================================================ FILE: client/web/addons/networking/.eslintignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/networking/.eslintrc.js ================================================ 'use strict'; const { configs } = require('@nullvoxpopuli/eslint-configs'); module.exports = configs.ember(); ================================================ FILE: client/web/addons/networking/.gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /.env* /.pnp* /.sass-cache /connect.lock /coverage/ /libpeerconnection.log /npm-debug.log* /testem.log /yarn-error.log # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/networking/.npmignore ================================================ # compiled output /dist/ /tmp/ # dependencies /bower_components/ # misc /.bowerrc /.editorconfig /.ember-cli /.env* /.eslintignore /.eslintrc.js /.git/ /.gitignore /.template-lintrc.js /.travis.yml /.watchmanconfig /bower.json /config/ember-try.js /CONTRIBUTING.md /ember-cli-build.js /testem.js /tests/ /yarn.lock .gitkeep # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/networking/.template-lintrc.js ================================================ 'use strict'; module.exports = require('@emberclear/config/.template-lintrc'); ================================================ FILE: client/web/addons/networking/.watchmanconfig ================================================ { "ignore_dirs": ["tmp", "dist"] } ================================================ FILE: client/web/addons/networking/CONTRIBUTING.md ================================================ # How To Contribute ## Installation * `git clone ` * `cd networking` * `yarn install` ## Linting * `yarn lint:hbs` * `yarn lint:js` * `yarn lint:js --fix` ## Running tests * `ember test` – Runs the test suite on the current Ember version * `ember test --server` – Runs the test suite in "watch mode" * `ember try:each` – Runs the test suite against multiple Ember versions ## Running the dummy application * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). ================================================ FILE: client/web/addons/networking/LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: client/web/addons/networking/README.md ================================================ networking ============================================================================== [Short description of the addon.] Compatibility ------------------------------------------------------------------------------ * Ember.js v3.16 or above * Ember CLI v2.13 or above * Node.js v10 or above Installation ------------------------------------------------------------------------------ ``` ember install networking ``` Usage ------------------------------------------------------------------------------ [Longer description of how to use the addon in apps.] Contributing ------------------------------------------------------------------------------ See the [Contributing](CONTRIBUTING.md) guide for details. License ------------------------------------------------------------------------------ This project is licensed under the [MIT License](LICENSE.md). ================================================ FILE: client/web/addons/networking/addon/errors.ts ================================================ export class UnknownMessageError extends Error {} export class DataTransferFailed extends Error {} export class CurrentUserMustHaveAName extends Error {} ================================================ FILE: client/web/addons/networking/addon/index.ts ================================================ export { default as Message } from './models/message'; export { default as Relay } from './models/relay'; export { ensureRelays } from './required-data'; export { default as ConnectionService } from './services/connection'; export { EphemeralConnection } from './services/connection/ephemeral/ephemeral-connection'; export { default as ConnectionStatus } from './services/connection/status'; export { default as MessageDispatcher } from './services/messages/dispatcher'; export { default as MessageFactory } from './services/messages/factory'; export { Connection } from './utils/connection/connection'; ================================================ FILE: client/web/addons/networking/addon/models/message/utils.ts ================================================ import { TARGET, TYPE } from '../message'; import type Message from '../message'; type RecordArray = Array; export function selectUnreadDirectMessages( messages: Message[] | RecordArray | TODO, fromId: string ): Message[] { const filtered = selectUnreadMessages(messages).filter((m) => { return m.from === fromId; }); return filtered; } export function selectUnreadMessages(messages: Message[] | RecordArray | TODO): Message[] { const filtered = messages.filter((m: Message) => { return ( // ember-data in-flight messages // don't yet have any fields m.from && m.unread && // ensure the correct type of message m.target !== TARGET.NONE && m.target !== TARGET.MESSAGE && m.type !== TYPE.PING ); }); return filtered; } export async function markAsRead(message: Message) { message.readAt = new Date(); await message.save(); } export function messagesForDM( messages: RecordArray | TODO, me: string, chattingWithId: string ): Message[] { let result = messages.filter((message: Message) => { return isMessageDMBetween(message, me, chattingWithId); }); return result; } export function isMessageDMBetween(message: Message, me: string, chattingWithId: string) { const isRelevant = message.target === TARGET.WHISPER && message.type === TYPE.CHAT && // we sent this message to someone else (this could incude ourselves) ((message.to === chattingWithId && message.from === me) || // we received a message from someone else to us (including from ourselves) (message.from === chattingWithId && message.to === me)); return isRelevant; } ================================================ FILE: client/web/addons/networking/addon/models/message.ts ================================================ import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import type { Identity } from '@emberclear/local-account'; export const MESSAGE_LIMIT = 75; /** * types: * * CHAT: a standard message sent to a person or room. * * EMOTE: same as chat, but with special formatting for * talking about oneself in the 3rd person. * * WHISPER: same as chat, but explictly only intended for a single person * * PING: a system message used to determine who is online upon app-boot * * DELIVERY_CONFIRMATION: a system message automatically sent back to someone * who sent you a message so that they know you received it * * DISCONNECT: a courtesy message to notify your contacts that you * are about to go offline. * * INFO_CHANNEL_SYNC: a system message that is sent without a body to request channel context and returned with a body of channel context * * CHANNEL_VOTE: a system message used to distribute votes within a channel * * Properties of: * Chat, Emote * - channel: the id of the channel this message is intended for * NOTE: additional channel properties (such as encryption, members, etc) * will ultimately be stored on the channel. * However, in order to make sure everyone's member list is up to date, * the member list will be sent along wich each message * TODO: decide whether these extra properties live in the body json * TODO: do we want structureless data in the body? * * Whisper, Ping, Disconnect * - no properties that alter behavior / message routing * * Currently Unused Properties: * - contentType, thread * * Currently Unused Message Types: * - emote, delivery confirmation, info channel sync, channel vote * * */ export enum TYPE { CHAT = 'chat', EMOTE = 'emote', PING = 'ping', DISCONNECT = 'disconnect', DELIVERY_CONFIRMATION = 'delivery-confirmation', INFO_CHANNEL_SYNC_REQUEST = 'info-channel-sync-request', INFO_CHANNEL_SYNC_FULFILL = 'info-channel-sync-fulfill', CHANNEL_VOTE = 'channtel-vote', } export enum TARGET { NONE = '', WHISPER = 'whisper', CHANNEL = 'channel', MESSAGE = 'message', } /** * NOTE: * GUID - used for message receipts / delivery confirmation * and threads * */ export default class Message extends Model { /** * from: the id of an identity * */ @attr() from!: string; /** * identityId | channelId | messageId * * TODO: should these have different formats?; * TODO: change this from ids to a polymorphic belongs to * */ @attr() to!: string; /** * Contents of body may depend on the TYPE/TARGET * */ @attr() body!: string; /** * Additional information for aiding in protocols * * For example: * * In channel messages the following needs to be included, * - the creator of the channel * - the member list * - invites are pending * - who in the channel has approved the invites (for consensus) * - blacklisted members (blacklist by consensus as well) * * TODO: maybe in the first iteration of channels, just the channel creator * can perform memberlist changes * */ @attr() metadata!: Record; @attr() type!: TYPE; @attr() target!: TARGET; @attr() thread!: string; @attr() receivedAt?: Date; @attr() sentAt!: Date; /** * The Date/Time that the current user has viewed the message. * Can also be artifically set via a "Mark all as read" button. * */ @attr() readAt!: Date; @attr() sendError?: string; /** * When a user comes online, they dispatch a bunch of pings to their contacts. * If any of those contacts have queue messages (designated by this boolean) * the messages will automatically be sent to the user who jest came online * */ @attr() queueForResend?: boolean; @belongsTo('identity', { async: false, polymorphic: true }) sender?: Identity; // @belongsTo('message', { async: false, inverse: 'deliveryConfirmations' }) confirmationFor?: Message; // @hasMany('message', { async: false, inverse: 'confirmationFor' }) deliveryConfirmations?: Message[]; @hasMany('message', { async: false }) deliveryConfirmations?: Message[]; get unread() { return !this.readAt; } // currently unused @attr() contentType!: string; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { message: Message; } } ================================================ FILE: client/web/addons/networking/addon/models/relay.ts ================================================ import { tracked } from '@glimmer/tracking'; import Model, { attr } from '@ember-data/model'; export default class Relay extends Model { @attr('string') socket!: string; @attr('string') og!: string; @attr('string') host!: string; @attr() priority!: number; @tracked connectionCount = 0; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { relay: Relay; } } ================================================ FILE: client/web/addons/networking/addon/required-data.ts ================================================ import Ember from 'ember'; import type ApplicationInstance from '@ember/application/instance'; import type { Relay } from '@emberclear/networking'; export const defaultRelays = [ { socket: 'wss://mesh-relay-in-us-1.herokuapp.com/socket', og: 'https://mesh-relay-in-us-1.herokuapp.com/open_graph', host: 'mesh-relay-in-us-1.herokuapp.com', }, // { // socket: 'wss://mesh-relay-eu-1.herokuapp.com/socket', // og: 'https://mesh-relay-eu-1.herokuapp.com/open_graph', // host: 'mesh-relay-eu-1.herokuapp.com', // }, // { // socket: 'ws://localhost:4301/socket', // og: 'http://localhost:4301/open_graph', // host: 'localhost:4301', // }, ]; export async function ensureRelays(applicationInstance: ApplicationInstance) { if (Ember.testing) return; const store = applicationInstance.lookup('service:store'); const existing = await store.findAll('relay'); const existingHosts = existing.map((e: Relay) => e.host); return await Promise.all( defaultRelays.map((defaultRelay, i) => { if (existingHosts.includes(defaultRelay.host)) { return; } const record = store.createRecord('relay', { ...defaultRelay, priority: i + 1, }); return record.save(); }) ); } ================================================ FILE: client/web/addons/networking/addon/services/connection/ephemeral/ephemeral-connection.ts ================================================ import { getOwner, setOwner } from '@ember/application'; import { assert } from '@ember/debug'; import { associateDestroyableChild, isDestroyed, isDestroying, registerDestructor, } from '@ember/destroyable'; import { inject as service } from '@ember/service'; import { CryptoConnector } from '@emberclear/crypto'; import { fromHex, toHex } from '@emberclear/encoding/string'; import { Connection } from '@emberclear/networking'; import { defaultRelays } from '@emberclear/networking/required-data'; import { pool } from '@emberclear/networking/utils/connection/connection-pool'; import type StoreService from '@ember-data/store'; import type { WorkersService } from '@emberclear/crypto'; import type { EncryptableObject, EncryptedMessage, KeyPair } from '@emberclear/crypto/types'; import type { EndpointInfo } from '@emberclear/networking/types'; import type { ConnectionPool, STATUS, } from '@emberclear/networking/utils/connection/connection-pool'; type Target = { pub: Uint8Array; hex: string; }; const DEFAULT_GETTER = () => defaultRelays; export type GetEndpoints = () => EndpointInfo[] | Promise; export interface Options { getEndpoints?: GetEndpoints | undefined; publicKeyAsHex?: string | undefined; keys?: KeyPair | undefined; } export class EphemeralConnection { @service declare store: StoreService; @service declare workers: WorkersService; // setup in the psuedo constructor (static method: build) // (build is an "async constructor") declare connectionPool: ConnectionPool; declare crypto: CryptoConnector; declare hexId: string; /** * Static information about who we're connecting to * - useful if the connection is only meant for one person */ declare target?: Target; getEndpoints: GetEndpoints = () => []; /** * For creating new instances of ephemeral connections * within ember apps. * ( requires on object with an owner and is destroyable ) * * This is deliberately not called create, because * this should not be called be the Ember D.I. System * as creation of these instances is *async*... and async * constructors don't exist. * * Additionally, it has a different signature. * There is no "automatic" way to have a destroyable created, * so given a "caller", we can register the destructor. * * When this method finishes running, you will have * - public/private keys * - a new connection(pool) to the relays * * @param parent Ember Container Object (must be destroyable) * @param publicKeyAsHex string */ static async build( /* hack to get inheritence in static methods */ this: { new (hex?: string): SubClass }, /* the actual params to this method */ // eslint-disable-next-line @typescript-eslint/ban-types parent: object, options?: Options ): Promise { let { getEndpoints, publicKeyAsHex, keys } = options || {}; let instance = new this(publicKeyAsHex); setOwner(instance, getOwner(parent)); associateDestroyableChild(parent, instance); registerDestructor(instance, instance.teardown.bind(instance)); await instance.hydrateCrypto(keys); assert('Crypto failed to initialize', instance.crypto); assert('Failed to generate an ephemeral identifier', instance.hexId); instance.getEndpoints = getEndpoints || DEFAULT_GETTER; await instance.establishConnection(); assert('Connection Pool failed to be set up', instance.connectionPool); return instance; } constructor(publicKeyAsHex?: string) { this.setTarget(publicKeyAsHex); } setTarget(publicKeyAsHex?: string) { if (publicKeyAsHex) { this.target = { pub: fromHex(publicKeyAsHex), hex: publicKeyAsHex, }; } } setCrypto(keys: KeyPair) { this.crypto.keys = keys; this.hexId = toHex(keys.publicKey); } disconnect() { if (this.connectionPool) { this.connectionPool.drain(); } } teardown() { this.disconnect(); } onData(_data: EncryptedMessage) { throw new Error('onData must be overridden in a subclass'); } /////////////////////////////////////// async hydrateCrypto(keys?: KeyPair) { let { hex, crypto } = await generateEphemeralKeys(this.workers, keys); if (isDestroying(this) || isDestroyed(this)) return; this.crypto = crypto; this.hexId = hex; } sendToHex(message: EncryptableObject, hexPub: string) { let pub = fromHex(hexPub); return this.send(message, { hex: hexPub, pub }); } async send(message: EncryptableObject, target?: Target) { if (isDestroying(this) || isDestroyed(this)) return; let _target = this.target || target; if (!_target) { throw new Error('Cannot send a message with no target'); } if (!this.connectionPool) { await this.establishConnection(); if (isDestroying(this) || isDestroyed(this)) return; // throw new Error('Cannot send a message with no connection to the target'); } let to = _target.pub; let connection = await this.connectionPool.acquire(); if (isDestroying(this) || isDestroyed(this)) return; let encryptedMessage = await this.crypto.encryptForSocket({ ...message }, { publicKey: to }); if (isDestroying(this) || isDestroyed(this)) return; await connection.send({ to: toHex(to), message: encryptedMessage }); } async establishConnection() { if (this.connectionPool) return; if (isDestroying(this) || isDestroyed(this)) return; this.connectionPool = await pool({ endpoints: await this.getEndpoints(), create: createConnection.bind(null, this.hexId, this.onData), destroy: (instance) => instance.destroy(), isOk: (instance) => instance.isConnected, onStatusChange: (status: STATUS) => console.info({ status }), minConnections: 1, }); } } async function generateEphemeralKeys(workers: WorkersService, keys?: KeyPair) { let crypto = new CryptoConnector({ workerService: workers }); if (!keys) { keys = await crypto.generateKeys(); } crypto.keys = keys; let hex = toHex(keys.publicKey); return { hex, crypto, ...keys }; } async function createConnection( publicKey: string, onData: (data: EncryptedMessage) => void, relay: EndpointInfo ) { let instance = new Connection({ relay, onData, publicKey, onInfo: (_info) => ({}), }); await instance.connect(); return instance; } ================================================ FILE: client/web/addons/networking/addon/services/connection/manager.ts ================================================ import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { Connection } from '@emberclear/networking'; import { pool } from '@emberclear/networking/utils/connection/connection-pool'; import type ArrayProxy from '@ember/array/proxy'; import type StoreService from '@ember-data/store'; import type { CurrentUserService } from '@emberclear/local-account'; import type { ConnectionStatus, Relay } from '@emberclear/networking'; import type MessageProcessor from '@emberclear/networking/services/messages/processor'; import type { OpenGraphData } from '@emberclear/networking/types'; import type { ConnectionPool, STATUS, } from '@emberclear/networking/utils/connection/connection-pool'; export default class ConnectionManager extends Service { @service declare store: StoreService; @service('messages/processor') declare processor: MessageProcessor; @service('connection/status') declare status: ConnectionStatus; @service declare currentUser: CurrentUserService; declare connectionPool?: ConnectionPool; async getOpenGraph(url: string): Promise { if (!this.connectionPool) { return {}; } let connection = await this.connectionPool.acquire(); let safeUrl = encodeURIComponent(url); let ogUrl = `${connection.relay.og}?url=${safeUrl}`; let response = await fetch(ogUrl, { credentials: 'omit', referrer: 'no-referrer', cache: 'no-cache', headers: { ['Accept']: 'application/json', }, }); try { let json = await response.json(); return (json || {}).data; } catch (e) { return {}; } } acquire() { if (!this.connectionPool) { return; } return this.connectionPool.acquire(); } async setup() { if (this.connectionPool) { return; } let relays: ArrayProxy = await this.store.findAll('relay'); // TODO: // figure out how to handle message received concurrency. // - what happens if the connection pool is all connected to // the same relay? // - should we prevent the ability to connect to the same relay // multiple times // - what happens if we can't mean our min-connections? this.connectionPool = await pool({ endpoints: relays.toArray(), create: this.createConnection.bind(this), destroy: (instance) => instance.destroy(), isOk: (instance) => instance.isConnected, onStatusChange: this.updateStatus.bind(this), minConnections: 1, }); } disconnect() { if (this.connectionPool) { this.connectionPool.drain(); } } willDestroy() { this.disconnect(); return super.destroy(); } private updateStatus(status: STATUS) { this.status.updateStatus(status); } private async createConnection(relay: Relay) { let instance = new Connection({ relay, publicKey: this.currentUser.uid, onData: this.processor.receive.bind(this.processor), onInfo: (info) => { // TODO: Temporary // TODO: This needs to be pulled out into a web worker (the whole class) // TODO: don't set these directly on the relay? Object.assign(relay, info); }, }); // Do connect / subscribe, etc await instance.connect(); return instance; } } declare module '@ember/service' { interface Registry { 'connection/manager': ConnectionManager; } } ================================================ FILE: client/web/addons/networking/addon/services/connection/status.ts ================================================ import { tracked } from '@glimmer/tracking'; import Service from '@ember/service'; import { timeout } from 'ember-concurrency'; import { restartableTask } from 'ember-concurrency-decorators'; import { taskFor } from 'ember-concurrency-ts'; import { STATUS_CONNECTED, STATUS_CONNECTING, STATUS_DEGRADED, STATUS_DISCONNECTED, STATUS_UNKNOWN, } from '@emberclear/networking/utils/connection/connection-pool'; import type { STATUS } from '@emberclear/networking/utils/connection/connection-pool'; const STATUS_LEVEL_MAP = { [STATUS_UNKNOWN]: 'warning', [STATUS_DEGRADED]: 'warning', [STATUS_CONNECTED]: 'info', [STATUS_CONNECTING]: 'info', [STATUS_DISCONNECTED]: 'danger', }; export default class ConnectionStatusService extends Service { @tracked hasUpdate = false; @tracked hadUpdate = false; @tracked text = ''; @tracked level = ''; get isConnected() { switch (this.text) { case STATUS_CONNECTED: case STATUS_DEGRADED: return true; default: return false; } } get isConnecting() { return this.text === STATUS_CONNECTING; } updateStatus(text: STATUS) { this.text = text; this.level = STATUS_LEVEL_MAP[text]; // eslint-disable-next-line @typescript-eslint/no-floating-promises taskFor(this.showStatusChange).perform(); } @restartableTask({ withTestWaiter: true }) async showStatusChange() { this.hasUpdate = true; this.hadUpdate = false; await timeout(2000); this.hadUpdate = true; await timeout(1000); this.hasUpdate = false; } } declare module '@ember/service' { interface Registry { 'connection/status': ConnectionStatusService; } } ================================================ FILE: client/web/addons/networking/addon/services/connection.ts ================================================ import Service, { inject as service } from '@ember/service'; import { dropTask } from 'ember-concurrency-decorators'; import { taskFor } from 'ember-concurrency-ts'; import type { CurrentUserService } from '@emberclear/local-account'; import type { Message } from '@emberclear/networking'; import type ConnectionManager from '@emberclear/networking/services/connection/manager'; import type ContactsOnlineChecker from '@emberclear/networking/services/contacts/online-checker'; import type MessageDispatcher from '@emberclear/networking/services/messages/dispatcher'; import type { OutgoingPayload } from '@emberclear/networking/utils/connection/connection'; type ConnectionHooks = { onReceive(message: Message): Promise; }; export default class ConnectionService extends Service { @service declare currentUser: CurrentUserService; @service('connection/manager') declare manager: ConnectionManager; @service('messages/dispatcher') declare dispatcher: MessageDispatcher; @service('contacts/online-checker') declare onlineChecker: ContactsOnlineChecker; hooks?: ConnectionHooks; connect() { return taskFor(this._connect).perform(); } disconnect() { this.manager.disconnect(); } async getOpenGraph(url: string) { return this.manager.getOpenGraph(url); } async send(payload: OutgoingPayload) { let instance = await this.manager.acquire(); if (instance) { await instance.send(payload); } } @dropTask({ withTestWaiter: true }) private async _connect() { let canConnect = await this.canConnect(); if (!canConnect) return; await this.manager.setup(); await this.dispatcher.pingAll(); return taskFor(this.onlineChecker.checkOnlineStatus).perform(); } private canConnect(): Promise { return this.currentUser.exists(); } } declare module '@ember/service' { interface Registry { connection: ConnectionService; } } ================================================ FILE: client/web/addons/networking/addon/services/contacts/online-checker.ts ================================================ import Ember from 'ember'; import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { timeout } from 'ember-concurrency'; import { task } from 'ember-concurrency-decorators'; import { taskFor } from 'ember-concurrency-ts'; import { Status } from '@emberclear/local-account'; import type StoreService from '@ember-data/store'; import type { Contact } from '@emberclear/local-account'; import type { MessageDispatcher, MessageFactory } from '@emberclear/networking'; const THIRTY_SECONDS = 30000; export default class ContactsOnlineChecker extends Service { @service declare store: StoreService; @service('messages/dispatcher') declare dispatcher: MessageDispatcher; @service('messages/factory') declare messageFactory: MessageFactory; @task({ withTestWaiter: true }) async checkOnlineStatus() { if (Ember.testing) return; // eslint-disable-next-line no-constant-condition while (true) { await timeout(THIRTY_SECONDS); const ping = this.messageFactory.buildPing(); this.store .peekAll('contact') .filter((contact: Contact) => contact.onlineStatus !== Status.OFFLINE) .forEach((contact) => { return taskFor(this.dispatcher.sendToUser).perform(ping, contact); }); } } } declare module '@ember/service' { interface Registry { 'contacts/online-checker': ContactsOnlineChecker; } } ================================================ FILE: client/web/addons/networking/addon/services/messages/-utils/builder.ts ================================================ import type { Message } from '@emberclear/networking'; interface Sender { name: string; uid: string; } export function buildSender(sender: Sender) { return { name: sender.name, uid: sender.uid, location: '', }; } export function buildMessage(msg: Message) { const { body, contentType } = msg; return { body, contentType, }; } export function build(msg: Message, sender: Sender) { return { id: msg.id, to: msg.to, type: msg.type, target: msg.target, client: '', ['client_version']: '', ['time_sent']: msg.sentAt, sender: buildSender(sender), message: buildMessage(msg), }; } ================================================ FILE: client/web/addons/networking/addon/services/messages/auto-responder.ts ================================================ import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { taskFor } from 'ember-concurrency-ts'; import type StoreService from '@ember-data/store'; import type { Contact } from '@emberclear/local-account'; import type { Message, MessageDispatcher, MessageFactory } from '@emberclear/networking'; /** * Nothing here should be blocking, as these responses should not matter * to the receiver, but are for the sender's benefit. * * It is up to the invoker to not await these methods. * */ export default class MessageAutoResponder extends Service { @service('messages/dispatcher') declare dispatcher: MessageDispatcher; @service('messages/factory') declare factory: MessageFactory; @service declare store: StoreService; async messageReceived(respondToMessage: Message) { const sender = respondToMessage.sender; const response = this.factory.buildDeliveryConfirmation(respondToMessage); if (sender) { // eslint-disable-next-line @typescript-eslint/no-floating-promises taskFor(this.dispatcher.sendToUser).perform(response, sender); } } async cameOnline(contact: Contact) { const pendingMessages = await this.store.query('message', { queueForResend: true, to: contact.uid, }); pendingMessages.forEach(async (message: Message) => { message.queueForResend = false; await message.save(); return taskFor(this.dispatcher.sendToUser).perform(message, contact); }); } } declare module '@ember/service' { interface Registry { 'messages/auto-responder': MessageAutoResponder; } } ================================================ FILE: client/web/addons/networking/addon/services/messages/dispatcher.ts ================================================ import { assert } from '@ember/debug'; import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { task } from 'ember-concurrency-decorators'; import { taskFor } from 'ember-concurrency-ts'; import { toHex } from '@emberclear/encoding/string'; import { Contact, User } from '@emberclear/local-account'; import { build as toPayloadJson } from './-utils/builder'; import type StoreService from '@ember-data/store'; import type { Channel, CurrentUserService } from '@emberclear/local-account'; import type { ConnectionService, Message, MessageFactory } from '@emberclear/networking'; import type StatusManager from '@emberclear/networking/services/status-manager'; export default class MessageDispatcher extends Service { @service declare store: StoreService; @service declare connection: ConnectionService; @service declare currentUser: CurrentUserService; @service declare statusManager: StatusManager; @service('messages/factory') declare messageFactory: MessageFactory; async send(text: string, to: Contact | Channel) { const message = this.messageFactory.buildChat(text, to); await message.save(); this.sendTo(message, to); } // there needs to be a polymorphic relationship in order for this to work // sendMessage(message: Message) { // return sendTo(message, message.to); // } sendTo(message: Message, to: Contact | Channel) { message.queueForResend = false; if (to instanceof User) { return; } if (to instanceof Contact) { taskFor(this.sendToUser).perform(message, to); return; } // Otherwise, Channel Message this.sendToChannel(message, to); } async pingAll() { const ping = this.messageFactory.buildPing(); // eslint-disable-next-line @typescript-eslint/no-floating-promises taskFor(this.sendToAll).perform(ping); } // the downside to end-to-end encryption // the bigger the list of identities, the longer this takes // // TODO: should this be hard-limited to just messages like PINGs? @task async sendToAll(msg: Message) { const everyone = await this.store.findAll('contact'); everyone.forEach((contact: Contact) => { return taskFor(this.sendToUser).perform(msg, contact); }); } sendToChannel(msg: Message, channel: Channel) { const members = channel.contextChain.members; members.forEach((member) => { if (member.id === this.currentUser.id) return; // don't send to self return taskFor(this.sendToUser).perform(msg, member); }); } @task async sendToUser(msg: Message, to: Contact) { if (!this.currentUser.crypto) { console.info('Crypto Worker not available'); return; } const theirPublicKey = to.publicKey as Uint8Array; const uid = toHex(theirPublicKey); assert(`expected currentUser to exist`, this.currentUser.record); const payload = toPayloadJson(msg, this.currentUser.record); const encryptedMessage = await this.currentUser.crypto.encryptForSocket(payload, to); try { await this.connection.send({ to: uid, message: encryptedMessage }); msg.receivedAt = new Date(); } catch (e) { const { reason, to_uid: toUid } = e; if (reason) { const error: string = reason; msg.sendError = error; if (error.match(/not found/)) { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.statusManager.markOffline(toUid); return; } else if (error.match(/timed out/)) { return; } } console.debug(e.name, e); } } } declare module '@ember/service' { interface Registry { 'messages/dispatcher': MessageDispatcher; } } ================================================ FILE: client/web/addons/networking/addon/services/messages/factory.ts ================================================ import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { v4 as uuid } from 'uuid'; import { Identity } from '@emberclear/local-account'; import Channel from '@emberclear/local-account/models/channel'; import { TARGET, TYPE } from '@emberclear/networking/models/message'; import type StoreService from '@ember-data/store'; // import { buildChannelInfo, buildVote } from '../channels/-utils/channel-factory'; import type { CurrentUserService } from '@emberclear/local-account'; // import type Vote from '@emberclear/local-account/models/vote'; import type { Message } from '@emberclear/networking'; import type { P2PMessage } from '@emberclear/networking/types'; export default class MessageFactory extends Service { @service declare store: StoreService; @service declare currentUser: CurrentUserService; buildNewReceivedMessage(json: P2PMessage, sender: Identity) { const { id, type, target, message: msg } = json; const message = this.store.createRecord('message', { id, type, target, sender, from: sender.uid, to: this.currentUser.uid, sentAt: new Date(json.time_sent), receivedAt: new Date(), body: msg.body, // thread: msg.thread, contentType: msg.contentType, }); return message; } buildChat(text: string, to: Identity | Channel) { let attributes = {}; if (to instanceof Identity) { attributes = { target: TARGET.WHISPER, to: to.uid }; } else if (to instanceof Channel) { attributes = { target: TARGET.CHANNEL, to: to.id, // channelInfo: buildChannelInfo(to), }; } let message = this.build({ body: text, type: TYPE.CHAT, // all messages sent are read... beacuse.. // we sent them, so... they are read already... readAt: new Date(), ...attributes, }); return message; } // buildChannelVote(vote: Vote, to: Channel) { // return this.build({ // type: TYPE.CHANNEL_VOTE, // to: to.id, // metadata: buildVote(vote), // channelInfo: buildChannelInfo(to), // }); // } // buildChannelInfoSyncRequest(to: Channel) { // return this.build({ // type: TYPE.INFO_CHANNEL_SYNC_REQUEST, // to: to.id, // channelInfo: buildChannelInfo(to), // }); // } // buildChannelInfoSyncFulfill(to: Identity, data: Channel) { // return this.build({ // type: TYPE.INFO_CHANNEL_SYNC_FULFILL, // to: to.uid, // channelInfo: buildChannelInfo(data), // }); // } buildPing() { return this.build({ type: TYPE.PING }); } buildDeliveryConfirmation(forMessage: Message): Message { return this.build({ target: TARGET.MESSAGE, type: TYPE.DELIVERY_CONFIRMATION, to: forMessage.id, }); } private build(attributes = {}) { return this.store.createRecord('message', { id: uuid(), sentAt: new Date(), from: this.currentUser.uid, sender: this.currentUser.record, ...attributes, }); } } declare module '@ember/service' { interface Registry { 'messages/factory': MessageFactory; } } ================================================ FILE: client/web/addons/networking/addon/services/messages/handler.ts ================================================ import Service, { inject as service } from '@ember/service'; import { isContact } from '@emberclear/local-account/utils'; import { MESSAGE_LIMIT, TARGET, TYPE } from '@emberclear/networking/models/message'; import { isMessageDMBetween, messagesForDM } from '@emberclear/networking/models/message/utils'; import type MessageFactory from './factory'; import type StoreService from '@ember-data/store'; import type { CurrentUserService } from '@emberclear/local-account'; import type ContactManager from '@emberclear/local-account/services/contact-manager'; import type { Message } from '@emberclear/networking'; import type AutoResponder from '@emberclear/networking/services/messages/auto-responder'; import type StatusManager from '@emberclear/networking/services/status-manager'; import type { P2PMessage } from '@emberclear/networking/types'; export default class ReceivedMessageHandler extends Service { @service declare store: StoreService; @service declare currentUser: CurrentUserService; @service declare contactManager: ContactManager; @service declare statusManager: StatusManager; @service('messages/factory') declare messageFactory: MessageFactory; @service('messages/auto-responder') declare autoResponder: AutoResponder; async handle(raw: P2PMessage) { let message = await this.decomposeMessage(raw); if (!message) { console.info('Message could not be decomposed', raw); return; } switch (message.type) { case TYPE.CHAT: return this.handleChat(message, raw); case TYPE.EMOTE: return this.handleChat(message, raw); case TYPE.DELIVERY_CONFIRMATION: return this.handleDeliveryConfirmation(message, raw); case TYPE.DISCONNECT: return this.handleDisconnect(message); case TYPE.INFO_CHANNEL_SYNC_REQUEST: return this.handleInfoChannelInfo(message, raw); case TYPE.PING: // do nothing, we do not need to send a response // at least for now, we have socket-level tools to know // when a message was sent successfully return message; default: console.info('Unrecognized message to handle...', raw); return message; } } private async handleDeliveryConfirmation(message: Message, raw: P2PMessage) { const targetMessage = await this.store.findRecord('message', raw.to); // targetMessage.set('confirmationFor', message); // TODO: see if ember data relationships can use normal push // eslint-disable-next-line @typescript-eslint/no-explicit-any (message.deliveryConfirmations as any).pushObject(targetMessage); // blocking? await message.save(); return message; } private async handleInfoChannelInfo(message: Message, _raw: P2PMessage) { return message; } private async handleDisconnect(message: Message) { // non-blocking // eslint-disable-next-line @typescript-eslint/no-floating-promises this.statusManager.markOffline(message.from); } private async handleChat(message: Message, raw: P2PMessage) { // non-blocking // eslint-disable-next-line @typescript-eslint/no-floating-promises this.autoResponder.messageReceived(message); switch (message.target) { case TARGET.WHISPER: return this.handleWhisperChat(message); case TARGET.CHANNEL: return this.handleChannelChat(message, raw); default: console.info('TARGET INVALID', raw); return message; } } private async handleWhisperChat(message: Message) { await this.trimMessages(message); await message.save(); if (message.sender) { message.sender.numUnread++; } return message; } private async handleChannelChat(message: Message, _raw: P2PMessage) { // TODO: if message is a channel message, deconstruct the channel info return message; } private async decomposeMessage(json: P2PMessage) { let { id, sender: senderInfo } = json; let sender = await this.findOrCreateSender(senderInfo); if (!isContact(sender)) { return; } await this.statusManager.markOnline(sender); await this.autoResponder.cameOnline(sender); try { // we've already received this message. // it's possible to receive the same message multiple // times if the sending client doesn't properly // make the message as sent let existing = await this.store.findRecord('message', id); return existing; } catch (e) { // we have not yet received this message // build a new message record return this.messageFactory.buildNewReceivedMessage(json, sender); } } /** * TODO: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB * TODO: https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/openCursor * * Trims messages for a message group down to 100.... because list occlusion isn't a thing yet * on the web (or is very very difficult to implement in JS) * * @param lastReceived this message is used to determine which chat DM / Channel the message * belongs to, and which set of messages will be trimmed. */ private async trimMessages(lastReceived: Message): Promise { let me = this.currentUser.uid; // if the most recently receive message belongs to a stack of DMs, // trim the DMs to be at most 100 messages. let isApplicableForTrim = isMessageDMBetween(lastReceived, me, lastReceived.from); if (isApplicableForTrim) { let allMessages = this.store.peekAll('message'); let forDM = messagesForDM(allMessages, me, lastReceived.from); let numTooMany = forDM.length - MESSAGE_LIMIT; if (numTooMany > 0) { let oldMessages = forDM.splice(0, numTooMany); await Promise.all(oldMessages.map((oldMessage: Message) => oldMessage.destroyRecord())); } } } private async findOrCreateSender(senderData: { uid: string; name: string }) { const { name, uid } = senderData; if (uid === this.currentUser.uid) { return this.currentUser.record; } return await this.contactManager.findOrCreate(uid, name); } } declare module '@ember/service' { interface Registry { 'messages/handler': ReceivedMessageHandler; } } ================================================ FILE: client/web/addons/networking/addon/services/messages/processor.ts ================================================ import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { enqueueTask } from 'ember-concurrency-decorators'; import { taskFor } from 'ember-concurrency-ts'; import type { EncryptedMessage } from '@emberclear/crypto/types'; import type { CurrentUserService } from '@emberclear/local-account'; import type { ConnectionService } from '@emberclear/networking'; import type ReceivedMessageHandler from '@emberclear/networking/services/messages/handler'; import type { P2PMessage } from '@emberclear/networking/types'; export default class MessageProcessor extends Service { @service declare currentUser: CurrentUserService; @service('messages/handler') declare handler: ReceivedMessageHandler; @service declare connection: ConnectionService; /** * Because we could potentially be receiving multiple * messages from a new contact, we need to queue multiple * calls to receive. * * Without queueing them, we can run in to concurrency issues * when interacting with the ember-data store / any "saving" * behavior. * */ receive(socketData: EncryptedMessage) { // eslint-disable-next-line @typescript-eslint/no-floating-promises taskFor(this._receive).perform(socketData); } @enqueueTask({ withTestWaiter: true, maxConcurrency: 1 }) async _receive(socketData: EncryptedMessage) { const decrypted = await this.currentUser.crypto.decryptFromSocket(socketData); let message = await this.handler.handle(decrypted); if (message) { await this.connection.hooks?.onReceive(message); } } } declare module '@ember/service' { interface Registry { 'messages/processor': MessageProcessor; } } ================================================ FILE: client/web/addons/networking/addon/services/status-manager.ts ================================================ import Service from '@ember/service'; import { inject as service } from '@ember/service'; import { Status } from '@emberclear/local-account/models/contact'; import type StoreService from '@ember-data/store'; import type { Contact } from '@emberclear/local-account'; import type ContactManager from '@emberclear/local-account/services/contact-manager'; // TODO: does this need to be its own service? // should these functions move to the ContactManager? export default class StatusManager extends Service { @service declare store: StoreService; @service declare contactManager: ContactManager; async markOffline(uid: string) { const contact = await this.contactManager.find(uid); if (!contact) return; contact.onlineStatus = Status.OFFLINE; return contact.save(); } async markOnline(uid: string | Contact) { let contact; if (typeof uid === 'string') { contact = await this.contactManager.find(uid); } else { contact = uid; } contact.onlineStatus = Status.ONLINE; } } declare module '@ember/service' { interface Registry { 'status-manager': StatusManager; } } ================================================ FILE: client/web/addons/networking/addon/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations", "paths": { "@emberclear/networking": ["."], "@emberclear/networking/*": ["./*"], "@ember/destroyable": ["../../../node_modules/ember-destroyable-polyfill"], "*": ["../types/*"] } }, "include": [".", "../types"], "references": [ { "path": "../../../libraries/questionably-typed" }, { "path": "../../../addons/crypto" }, { "path": "../../../addons/local-account" } ] } ================================================ FILE: client/web/addons/networking/addon/type-support.ts ================================================ import '@ember/service'; // Services need to be imported in order to be added to the Service Registry import './services/connection/status'; import './services/connection/manager'; import './services/contacts/online-checker'; import './services/messages/auto-responder'; import './services/messages/dispatcher'; import './services/messages/factory'; import './services/messages/handler'; import './services/messages/processor'; import './services/connection'; import './services/status-manager'; ================================================ FILE: client/web/addons/networking/addon/types.ts ================================================ export interface EndpointInfo { socket: string; og: string; } export interface P2PMessage { id: string; to: string; type: string; target: string; client: string; client_version: string; time_sent: Date; sender: { name: string; uid: string; location: string; }; message: { body: string; contentType: string; metadata?: Record; }; } export interface RelayStateJson { relay: { [key: string]: string }; ['connection_count']: number; ['connected_relays']: number; ['connected_to_relays']: Record; } export interface RelayState { relay: { [key: string]: string }; connectionCount?: number; connectedRelays?: number; connectedToRelays?: Record; } export interface OpenGraphData { audio?: string; ['audio:secure_url']?: string; ['audio:type']?: string; description?: string; determiner?: string; image?: string; ['image:alt']?: string; ['image:height']?: string; ['image:secure_url']?: string; ['image:width']?: string; locale?: string; site_name?: string; title?: string; type?: string; url?: string; video?: string; ['video:alt']?: string; ['video:height']?: string; ['video:secure_url']?: string; ['video:type']?: string; ['video:width']?: string; } export interface RelayOpenGraphResponse { data: OpenGraphData; } ================================================ FILE: client/web/addons/networking/addon/utils/connection/connection-pool.ts ================================================ import type { EndpointInfo } from '@emberclear/networking/types'; // min-connections are met export const STATUS_CONNECTED = 'connected'; // initial attempt at achieving min-connections export const STATUS_CONNECTING = 'connecting'; // min-connections are not met export const STATUS_DEGRADED = 'degraded'; // there are no connections export const STATUS_DISCONNECTED = 'disconnected'; // hopefully this is never used export const STATUS_UNKNOWN = 'unknown'; export type STATUS = | typeof STATUS_CONNECTED | typeof STATUS_CONNECTING | typeof STATUS_DEGRADED | typeof STATUS_DISCONNECTED | typeof STATUS_UNKNOWN; export interface PoolConfig { // send: >(instance, ...args: Args) => Promise; // Available URLs / whatever that will be randomly selected when creating // a new connection endpoints: EndpointInfo[]; // How to create and destroy instances // isOk is watched to know whether or not new // instances need to be made create: (endpoint: EndpointInfo) => Connectable | Promise; destroy: (instance: Connectable) => void | Promise; isOk: (instance: Connectable) => boolean; // hooks // After Initial setup, degraded may be a possibility // Connecting will only show up during the initial setup. // Connecting kinda means, no connections / degraded, but // we expected connections to happen onStatusChange: (status: STATUS) => void; // Min and Max number of instances to make minConnections?: number; maxConnections?: number; } /** * A pool is responsible for: * - creating new connections * - disposing of connections * * A pool is not responsible for: * - how to connect * - how to dispose * - receiving/sending messages * * * @param [PoolConfig] config; * */ export async function pool( config: PoolConfig ): Promise> { let connectionPool = new ConnectionPool(config); await connectionPool.hydrate(); return connectionPool; } export class ConnectionPool { private config: PoolConfig; private connections: Connectable[] = []; constructor(config: PoolConfig) { this.config = config; } get activeConnections() { return this.connections.filter(this.config.isOk); } get status(): STATUS { let count = this.activeConnections.length; if (count === 0) { return STATUS_DISCONNECTED; } else if (count < this.minConnections) { return STATUS_DEGRADED; } else if (count >= this.minConnections) { return STATUS_CONNECTED; } return STATUS_UNKNOWN; } get minConnections() { return this.config.minConnections || 1; } get minimumMet() { return this.activeConnections.length >= this.minConnections; } // TODO: implement logic for selecting the "best" connection async acquire(): Promise { await this.hydrate(); let pseudoBestIndex = Math.floor(Math.random() * this.activeConnections.length); return this.activeConnections[pseudoBestIndex]; } // TODO: we need a way to monitor status changes within // a connection async hydrate(): Promise { if (this.minimumMet) return; this.notifyOfStatusChange(STATUS_CONNECTING); for (let i = 0; i < this.minConnections; i++) { let endpoint = this.nextEndpoint(); if (!endpoint) { throw new Error( `No available endpoint. Are too many minimum connections specified? Current: ${this.minConnections}` ); } let connection = await this.config.create(endpoint); this.connections.push(connection); this.notifyOfStatusChange(); } } drain() { this.activeConnections.forEach(this.config.destroy); } private notifyOfStatusChange(status?: STATUS) { if (!this.config.onStatusChange) { return; } this.config.onStatusChange(status || this.status); } private nextEndpoint(): EndpointInfo { if (this.config.endpoints.length === 0) { throw new Error(`There are no endpoints in the connection pool`); } return this.config.endpoints[0]; } } ================================================ FILE: client/web/addons/networking/addon/utils/connection/connection.ts ================================================ import { Socket } from 'phoenix'; import type { EncryptedMessage } from '@emberclear/crypto/types'; import type { EndpointInfo, RelayState, RelayStateJson } from '@emberclear/networking/types'; import type { Channel } from 'phoenix'; export const NAME = Symbol('__PHOENIX_SOCKET__'); // Side-effect bad. // Need a better way to mock this rather than just // changing what this is assigned to. // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any)[NAME] = (window as any)[NAME] || Socket; interface Args { relay: EndpointInfo; publicKey: string; onData: (data: EncryptedMessage) => void; onInfo: (data: RelayState) => void; } export interface OutgoingPayload { to: string; message: string; } function phoenixSocket(): typeof Socket { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (window as any)[NAME]; } export class Connection { declare relay: EndpointInfo; declare url: string; declare publicKey: string; declare channelName: string; declare onData: (data: EncryptedMessage) => void; declare onInfo: (data: RelayState) => void; isConnected = false; isConnecting = false; private declare socket?: Socket; private declare channel?: Channel; /** * @param [Relay] relay * @param [string] publicKey: hex */ constructor({ relay, publicKey, onData, onInfo }: Args) { this.relay = relay; this.url = relay.socket; this.publicKey = publicKey; this.channelName = `user:${publicKey}`; this.onData = onData; this.onInfo = onInfo; } async connect() { if (this.isConnected || this.isConnecting) return; await this.setupSocket(); await this.setupChannels(); } private async setupSocket() { return new Promise((resolve, reject) => { let Klass = phoenixSocket(); this.isConnecting = true; this.socket = new Klass(this.url, { params: { uid: this.publicKey }, }); this.socket.onOpen(resolve); this.socket.onError(reject); this.socket.onClose(() => { this.isConnected = false; }); this.socket.connect(); }); } private async setupChannels() { await this.setupChatChannel(); await this.setupStatsChannel(); } private async setupStatsChannel() { return new Promise((resolve, reject) => { if (!this.socket) return reject(); let channel = this.socket.channel(`stats`, {}); channel.on('state', (data: RelayStateJson) => { let connectionCount = data['connection_count']; this.onInfo({ relay: data.relay, connectionCount }); }); channel .join() .receive('ok', () => { resolve(undefined); }) .receive('error', reject); }); } private async setupChatChannel() { return new Promise((resolve, reject) => { if (!this.socket) return reject(); this.channel = this.socket.channel(this.channelName, {}); this.channel.on('chat', this.onData); this.channel.onError(console.error); this.channel.onClose(() => { if (this.socket) { this.socket.disconnect(); } }); this.channel .join() .receive('ok', () => { this.isConnected = true; this.isConnecting = false; resolve(this.channel); }) .receive('error', (...args: unknown[]) => { return reject(...args); }) .receive('timeout', (...args: unknown[]) => { console.info('channel timed out', ...args); }); }); } send(payload: OutgoingPayload) { return new Promise((resolve, reject) => { if (!this.channel) { console.error('no channel present...'); return reject(); } this.channel .push('chat', payload) .receive('ok', resolve) .receive('error', reject) .receive('timeout', () => reject({ reason: 'timed out' })); }); } destroy() { if (this.socket) { this.socket.disconnect(); } } } ================================================ FILE: client/web/addons/networking/addon-test-support/index.ts ================================================ /* eslint-disable @typescript-eslint/no-explicit-any */ import { later, schedule } from '@ember/runloop'; import { NAME } from '@emberclear/networking/utils/connection/connection'; import type ApplicationInstance from '@ember/application/instance'; import type { Socket } from 'phoenix'; type Callback = (...args: unknown[]) => void; /** * Somewhat re-implements the relay behavior * * See: @emberclear/networking/utils/connection/connection.ts */ export function setupSocketServer(hooks: NestedHooks) { let oldSocket: Socket; let owner: ApplicationInstance | undefined; let users: Record> = {}; function FakeSocket(_url: string, _opts: Options) { return fakeServer; } const fakeServer: any = { onOpen: (fn: any) => (fakeServer._onOpen = fn), onError: () => { /* not needed */ }, onClose: () => { /* not needed */ }, connect: () => fakeServer._onOpen(), disconnect: () => { /* eh */ }, channel(channelName: string) { let id = ''; if (channelName.startsWith('user')) { let [, _id] = channelName.split(':'); id = _id; } const channel = { _handle: {} as Record, push(kind: string, payload: any) { const pushHandler = { _receive: {} as Record, /** * @public */ receive(kind: string, callback: Callback) { pushHandler._receive[kind] = callback; return pushHandler; }, }; switch (kind) { case 'chat': { let { to, message } = payload; // use runloop to hold up tests until this finishes schedule('afterRender', () => { if (!owner) return; if (!users[to]) { console.info({ users, payload, callback: pushHandler._receive?.error }); return pushHandler._receive?.error('user not found'); } users[to]._handle['chat']({ uid: id, message }); pushHandler._receive?.ok?.(); }); break; } default: console.debug('unknown push', { channelName, msg: kind, payload }); } return pushHandler; }, join() { return channel; }, receive(kind: string, callback: () => void) { switch (kind) { // on channel join case 'ok': requestAnimationFrame(callback); return channel; case 'error': // no tests of errors so far return channel; case 'timeout': // no tests of timeouts so far return channel; default: later( null, () => { console.debug('unnamed receive', { msg: kind }); callback(); }, 10 ); } return channel; }, on(msgType: string, callback: (data: T) => void) { channel._handle[msgType] = callback; }, leave() { /* not needed */ }, onClose() { /* not needed */ }, onError() { /* not needed */ }, }; if (id) { users[id] = channel; } return channel; }, }; hooks.beforeEach(function () { users = {}; oldSocket = (window as any)[NAME]; owner = this.owner; (window as any)[NAME] = FakeSocket; }); hooks.afterEach(function () { (window as any)[NAME] = oldSocket; }); } type Options = { params: { uid: string }; }; // export function mockSocketServer(url?: string) { // const fakeURL = url || `wss://${defaultRelays[0].host}/`; // const mockServer = new Server(fakeURL); // mockServer.on('connection', (socket) => { // console.log('connect', { socket, mockServer }); // socket.on('message', (data) => { // console.log({ data, socket, mockServer }); // socket.send('test message from mock server'); // }); // }); // return mockServer; // } ================================================ FILE: client/web/addons/networking/addon-test-support/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations/test-support", "paths": { "@emberclear/networking": ["../declarations"], "@emberclear/networking/*": ["../declarations/*"], "@emberclear/networking/test-support": ["."], "@emberclear/networking/test-support/*": ["./*"], "*": ["../types/*"] } }, "include": [".", "../types"], "references": [{ "path": "../addon" }] } ================================================ FILE: client/web/addons/networking/app/models/message.js ================================================ export { default } from '@emberclear/networking/models/message'; ================================================ FILE: client/web/addons/networking/app/models/relay.js ================================================ export { default } from '@emberclear/networking/models/relay'; ================================================ FILE: client/web/addons/networking/app/services/connection/manager.js ================================================ export { default } from '@emberclear/networking/services/connection/manager'; ================================================ FILE: client/web/addons/networking/app/services/connection/status.js ================================================ export { default } from '@emberclear/networking/services/connection/status'; ================================================ FILE: client/web/addons/networking/app/services/connection.js ================================================ export { default } from '@emberclear/networking/services/connection'; ================================================ FILE: client/web/addons/networking/app/services/contacts/online-checker.js ================================================ export { default } from '@emberclear/networking/services/contacts/online-checker'; ================================================ FILE: client/web/addons/networking/app/services/messages/auto-responder.js ================================================ export { default } from '@emberclear/networking/services/messages/auto-responder'; ================================================ FILE: client/web/addons/networking/app/services/messages/dispatcher.js ================================================ export { default } from '@emberclear/networking/services/messages/dispatcher'; ================================================ FILE: client/web/addons/networking/app/services/messages/factory.js ================================================ export { default } from '@emberclear/networking/services/messages/factory'; ================================================ FILE: client/web/addons/networking/app/services/messages/handler.js ================================================ export { default } from '@emberclear/networking/services/messages/handler'; ================================================ FILE: client/web/addons/networking/app/services/messages/processor.js ================================================ export { default } from '@emberclear/networking/services/messages/processor'; ================================================ FILE: client/web/addons/networking/app/services/status-manager.js ================================================ export { default } from '@emberclear/networking/services/status-manager'; ================================================ FILE: client/web/addons/networking/config/ember-try.js ================================================ 'use strict'; const getChannelURL = require('ember-source-channel-url'); module.exports = async function () { return { useYarn: true, scenarios: [ { name: 'ember-lts-3.16', npm: { devDependencies: { 'ember-source': '~3.16.0', }, }, }, { name: 'ember-lts-3.20', npm: { devDependencies: { 'ember-source': '~3.20.5', }, }, }, { name: 'ember-release', npm: { devDependencies: { 'ember-source': await getChannelURL('release'), }, }, }, { name: 'ember-beta', npm: { devDependencies: { 'ember-source': await getChannelURL('beta'), }, }, }, { name: 'ember-canary', npm: { devDependencies: { 'ember-source': await getChannelURL('canary'), }, }, }, { name: 'ember-default-with-jquery', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true, }), }, npm: { devDependencies: { '@ember/jquery': '^1.1.0', }, }, }, { name: 'ember-classic', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'application-template-wrapper': true, 'default-async-observers': false, 'template-only-glimmer-components': false, }), }, npm: { ember: { edition: 'classic', }, }, }, ], }; }; ================================================ FILE: client/web/addons/networking/config/environment.js ================================================ 'use strict'; module.exports = function (/* environment, appConfig */) { return {}; }; ================================================ FILE: client/web/addons/networking/ember-cli-build.js ================================================ 'use strict'; const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function (defaults) { let app = new EmberAddon(defaults, { // Add options here 'ember-cli-babel': { enableTypeScriptTransform: true, }, }); /* This build file specifies the options for the dummy test app of this addon, located in `/tests/dummy` This build file does *not* influence how the addon or the app using it behave. You most likely want to be modifying `./index.js` or app's build file */ return app.toTree(); }; ================================================ FILE: client/web/addons/networking/index.js ================================================ 'use strict'; module.exports = { name: require('./package').name, options: { 'ember-cli-babel': { enableTypeScriptTransform: true, }, }, // override isDevelopingAddon() { return true; }, }; ================================================ FILE: client/web/addons/networking/package.json ================================================ { "name": "@emberclear/networking", "version": "0.0.0", "description": "Handles all the networking concerns of emberclear projects", "keywords": [ "ember-addon" ], "repository": { "url": "https://github.com/NullVoxPopuli/emberclear", "directory": "client/web/addons/networking" }, "license": "GPL-3.0", "author": "NullVoxPopuli", "directories": { "doc": "doc", "test": "tests" }, "scripts": { "build": "ember build --environment=production", "lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*", "lint:hbs": "ember-template-lint .", "lint:js": "eslint .", "start": "ember serve", "test": "ember test", "test:try-one": "ember try:one", "test:ember-compatibility": "ember try:each" }, "dependencies": { "@emberclear/crypto": "*", "@emberclear/local-account": "*", "ember-auto-import": "1.11.3", "ember-cli-babel": "7.26.6", "ember-cli-htmlbars": "5.7.1", "ember-concurrency": "1.3.0", "ember-concurrency-async": "0.3.2", "ember-concurrency-decorators": "2.0.3", "ember-concurrency-test-waiter": "0.4.0", "ember-concurrency-ts": "0.2.2", "ember-destroyable-polyfill": "2.0.3", "phoenix": "1.5.9", "uuid": "8.3.2" }, "devDependencies": { "@ember/optional-features": "^2.0.0", "@emberclear/config": "*", "@emberclear/questionably-typed": "*", "@emberclear/test-helpers": "*", "@glimmer/component": "1.0.4", "@glimmer/tracking": "1.0.4", "@nullvoxpopuli/eslint-configs": "1.3.2", "@types/ember": "3.16.5", "@types/ember-data": "3.16.14", "@types/ember-data__model": "3.16.2", "@types/ember-data__store": "3.16.1", "@types/ember-qunit": "3.4.13", "@types/ember-resolver": "^5.0.10", "@types/ember__test-helpers": "2.0.0", "@types/phoenix": "1.5.1", "@types/qunit": "2.11.1", "@types/rsvp": "4.0.3", "@types/uuid": "8.3.1", "broccoli-asset-rev": "^3.0.0", "ember-cli": "3.26.1", "ember-cli-dependency-checker": "^3.2.0", "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "4.0.2", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", "ember-load-initializers": "2.1.2", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", "ember-resolver": "^8.0.2", "ember-source": "3.26.1", "ember-source-channel-url": "^3.0.0", "ember-try": "^1.4.0", "loader.js": "^4.7.0", "npm-run-all": "^4.1.5", "qunit-dom": "1.6.0" }, "engines": { "node": "10.* || >= 12" }, "typesVersions": { "*": { "*": [ "declarations/*", "declarations/*/index" ] } }, "volta": { "node": "14.17.1", "yarn": "1.22.10" }, "ember": { "edition": "octane" }, "ember-addon": { "configPath": "tests/dummy/config" } } ================================================ FILE: client/web/addons/networking/testem.js ================================================ 'use strict'; module.exports = require('@emberclear/config/testem'); ================================================ FILE: client/web/addons/networking/tests/dummy/app/app.ts ================================================ import './with-test-waiter'; import Application from '@ember/application'; import config from 'dummy/config/environment'; import loadInitializers from 'ember-load-initializers'; import Resolver from 'ember-resolver'; export default class App extends Application { modulePrefix = config.modulePrefix; podModulePrefix = config.podModulePrefix; Resolver = Resolver; } loadInitializers(App, config.modulePrefix); ================================================ FILE: client/web/addons/networking/tests/dummy/app/components/.gitkeep ================================================ ================================================ FILE: client/web/addons/networking/tests/dummy/app/config/environment.d.ts ================================================ export default config; /** * Type declarations for * import config from './config/environment' * * For now these need to be managed by the developer * since different ember addons can materialize new entries. */ declare const config: { environment: 'production' | 'test' | 'development'; modulePrefix: string; podModulePrefix: string; locationType: string; rootURL: string; host: string; APP: Record; }; ================================================ FILE: client/web/addons/networking/tests/dummy/app/controllers/.gitkeep ================================================ ================================================ FILE: client/web/addons/networking/tests/dummy/app/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/networking/tests/dummy/app/index.html ================================================ Dummy {{content-for "head"}} {{content-for "head-footer"}} {{content-for "body"}} {{content-for "body-footer"}} ================================================ FILE: client/web/addons/networking/tests/dummy/app/models/.gitkeep ================================================ ================================================ FILE: client/web/addons/networking/tests/dummy/app/router.ts ================================================ import EmberRouter from '@ember/routing/router'; import config from 'dummy/config/environment'; export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; } Router.map(function () {}); ================================================ FILE: client/web/addons/networking/tests/dummy/app/routes/.gitkeep ================================================ ================================================ FILE: client/web/addons/networking/tests/dummy/app/styles/app.css ================================================ ================================================ FILE: client/web/addons/networking/tests/dummy/app/templates/application.hbs ================================================ {{outlet}} ================================================ FILE: client/web/addons/networking/tests/dummy/app/with-test-waiter.js ================================================ import defineModifier from 'ember-concurrency-test-waiter/define-modifier'; defineModifier(); ================================================ FILE: client/web/addons/networking/tests/dummy/config/ember-cli-update.json ================================================ { "schemaVersion": "1.0.0", "packages": [ { "name": "ember-cli", "version": "3.21.2", "blueprints": [ { "name": "addon", "outputRepo": "https://github.com/ember-cli/ember-addon-output", "codemodsSource": "ember-addon-codemods-manifest@1", "isBaseBlueprint": true, "options": [ "--yarn" ] } ] } ] } ================================================ FILE: client/web/addons/networking/tests/dummy/config/environment.js ================================================ 'use strict'; module.exports = function (environment) { let ENV = { modulePrefix: 'dummy', environment, rootURL: '/', locationType: 'auto', EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true }, EXTEND_PROTOTYPES: { // Prevent Ember Data from overriding Date.parse. Date: false, }, }, APP: { // Here you can pass flags/options to your application instance // when it is created }, }; if (environment === 'development') { // ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_ACTIVE_GENERATION = true; // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; } if (environment === 'test') { // Testem prefers this... ENV.locationType = 'none'; // keep test console output quieter ENV.APP.LOG_ACTIVE_GENERATION = false; ENV.APP.LOG_VIEW_LOOKUPS = false; ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; } if (environment === 'production') { // here you can enable a production-specific feature } return ENV; }; ================================================ FILE: client/web/addons/networking/tests/dummy/config/optional-features.json ================================================ { "application-template-wrapper": false, "default-async-observers": true, "jquery-integration": false, "template-only-glimmer-components": true } ================================================ FILE: client/web/addons/networking/tests/dummy/config/targets.js ================================================ 'use strict'; const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; const isCI = Boolean(process.env.CI); const isProduction = process.env.EMBER_ENV === 'production'; if (isCI || isProduction) { browsers.push('ie 11'); } module.exports = { browsers, }; ================================================ FILE: client/web/addons/networking/tests/dummy/public/robots.txt ================================================ # http://www.robotstxt.org User-agent: * Disallow: ================================================ FILE: client/web/addons/networking/tests/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/networking/tests/index.html ================================================ Dummy Tests {{content-for "head"}} {{content-for "test-head"}} {{content-for "head-footer"}} {{content-for "test-head-footer"}} {{content-for "body"}} {{content-for "test-body"}} {{content-for "body-footer"}} {{content-for "test-body-footer"}} ================================================ FILE: client/web/addons/networking/tests/integration/.gitkeep ================================================ ================================================ FILE: client/web/addons/networking/tests/test-helper.ts ================================================ import { setApplication } from '@ember/test-helpers'; import { start } from 'ember-qunit'; import Application from 'dummy/app'; import config from 'dummy/config/environment'; setApplication(Application.create(config.APP)); start(); ================================================ FILE: client/web/addons/networking/tests/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", // Question: what's the best place for test and dummy declarations to go? They // aren't actually needed for anything other than to satisfy the requirements // for a composite build. "declarationDir": "./dummy/declarations", "paths": { "dummy/tests/*": ["./*"], "dummy/*": ["./dummy/app/*"], "@emberclear/networking": ["../declarations"], "@emberclear/networking/*": ["../declarations/*"] } }, "references": [ { "path": "../../test-helpers" }, { "path": "../addon" }, { "path": "../addon-test-support" } ] } ================================================ FILE: client/web/addons/networking/tests/unit/services/connection/status-test.js ================================================ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { timeout } from 'ember-concurrency'; import { STATUS_CONNECTED, STATUS_CONNECTING, STATUS_DEGRADED, STATUS_UNKNOWN, } from '@emberclear/networking/utils/connection/connection-pool'; import { getService } from '@emberclear/test-helpers/test-support'; module('Unit | Service | connection/status', function (hooks) { setupTest(hooks); // Replace this with your real tests. test('it exists', function (assert) { let service = getService('connection/status'); assert.ok(service); }); test('updating the status sets properties', async function (assert) { let service = getService('connection/status'); assert.equal(service.text, ''); assert.equal(service.level, ''); assert.notOk(service.hasUpdate); assert.notOk(service.hadUpdate); service.updateStatus(STATUS_CONNECTING); assert.equal(service.text, STATUS_CONNECTING); assert.equal(service.level, 'info'); assert.ok(service.hasUpdate); assert.notOk(service.hadUpdate); await timeout(2200); assert.ok(service.hadUpdate); assert.ok(service.hasUpdate); await timeout(1200); assert.notOk(service.hasUpdate); }); test('isConnected', function (assert) { let service = getService('connection/status'); service.updateStatus(STATUS_CONNECTING); assert.notOk(service.isConnected); service.updateStatus(STATUS_CONNECTED); assert.ok(service.isConnected); service.updateStatus(STATUS_UNKNOWN); assert.notOk(service.isConnected); service.updateStatus(STATUS_DEGRADED); assert.ok(service.isConnected); }); }); ================================================ FILE: client/web/addons/networking/tests/unit/services/messages/auto-responder-test.ts ================================================ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { v4 as uuid } from 'uuid'; import { clearLocalStorage, createContact, setupCurrentUser, } from '@emberclear/local-account/test-support'; import { TARGET, TYPE } from '@emberclear/networking/models/message'; import { getService, stubService, waitUntil } from '@emberclear/test-helpers/test-support'; import type { Identity } from '@emberclear/local-account'; import type { Message } from '@emberclear/networking'; import type AutoResponder from '@emberclear/networking/services/messages/auto-responder'; module('Unit | Service | messages/auto-responder', function (hooks) { setupTest(hooks); setupCurrentUser(hooks); clearLocalStorage(hooks); test('it exists', function (assert) { let service = getService('messages/auto-responder'); assert.ok(service); }); module('cameOnline', function () { module('handling messages queued for resend', function () { module('there are no pending messages', function (hooks) { hooks.beforeEach(async function (assert) { const store = getService('store'); const messages = await store.findAll('message'); assert.equal(messages.length, 0, 'there are no messages'); }); test('no messages are sent', async function (assert) { assert.expect(1); const service = getService('messages/auto-responder'); const somePerson = await createContact('some person'); stubService('messages/dispatcher', { sendToUser: { perform(/* _response: Message, _to: Identity */) { assert.ok(false, 'this method should not get called'); }, }, }); service.cameOnline(somePerson); }); }); module('there are pending messages', function (hooks) { let somePerson: Identity; let service: AutoResponder; hooks.beforeEach(async function (assert) { service = getService('messages/auto-responder'); somePerson = await createContact('some person'); const store = getService('store'); const me = getService('current-user'); let messages = await store.findAll('message'); assert.equal(messages.length, 0, 'there are no messages'); await store .createRecord('message', { to: somePerson.uid, queueForResend: true, sender: me.record, }) .save(); await store .createRecord('message', { to: somePerson.uid, queueForResend: true, sender: me.record, }) .save(); messages = await store.findAll('message'); assert.equal(messages.length, 2, 'there are 2 messages'); const pendingMessages = await store.query('message', { queueForResend: true, to: somePerson.uid, }); assert.equal(pendingMessages.length, 2, 'there are 2 pending messages'); }); test('there are no longer any queued messages', async function (assert) { assert.expect(6); stubService('messages/dispatcher', { sendToUser: { perform(_response: Message, to: Identity) { assert.equal(to.uid, somePerson.uid, `message sent to: ${somePerson.name}`); }, }, }); await service.cameOnline(somePerson); const store = getService('store'); await waitUntil(async () => { let messages = await store.query('message', { queueForResend: true, to: somePerson.uid, }); return messages.length === 0; }); const messages = await store.query('message', { queueForResend: true, to: somePerson.uid, }); assert.equal(messages.length, 0, 'there are no messages'); }); }); }); }); module('messageReceived', function () { test('a delivery confirmation is built', async function (assert) { assert.expect(5); const me = getService('current-user'); await me.exists(); const store = getService('store'); const service = getService('messages/auto-responder'); const sender = await createContact('some user'); const receivedMessage = store.createRecord('message', { id: uuid(), sender, body: 'test message', }); stubService('messages/dispatcher', { sendToUser: { perform(response: Message, to: Identity) { assert.equal(response.target, TARGET.MESSAGE); assert.equal(response.type, TYPE.DELIVERY_CONFIRMATION); assert.equal(response.to, receivedMessage.id); assert.equal(response.sender, me.record); assert.equal(to.publicKey, sender.publicKey); }, }, }); service.messageReceived(receivedMessage); }); }); }); ================================================ FILE: client/web/addons/networking/tests/unit/services/messages/handler-test.ts ================================================ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { v4 as uuid } from 'uuid'; import { attributesForContact, clearLocalStorage, setupCurrentUser, } from '@emberclear/local-account/test-support'; import { TARGET, TYPE } from '@emberclear/networking/models/message'; import { getService, getStore, stubService } from '@emberclear/test-helpers/test-support'; module('Unit | Service | messages/handler', function (hooks) { setupTest(hooks); setupCurrentUser(hooks); clearLocalStorage(hooks); test('it exists', function (assert) { let service = getService('messages/handler'); assert.ok(service); }); module('handle', function () { module('a chat message', function (hooks) { hooks.beforeEach(async function () { stubService('messages/auto-responder', { messageReceived() {}, cameOnline() {}, }); }); test('the message is saved', async function (assert) { const store = getStore(); const service = getService('messages/handler'); const me = getService('current-user'); const sender = await attributesForContact(); const before = await store.findAll('message'); const beforeCount = before.toArray().length; await service.handle({ id: uuid(), type: TYPE.CHAT, target: TARGET.WHISPER, to: me.record.uid, ['time_sent']: new Date(), client: 'tests', ['client_version']: '0', sender: { uid: sender.id, name: `user with id: ${sender.id}`, location: '', }, message: { body: 'malformed, cleartext body', contentType: 'is this used?', }, }); const after = await store.findAll('message'); assert.equal(after.length, beforeCount + 1); }); }); module('an emote message', function () {}); module('a delivery confirmation', function () {}); module('a disconnect message', function () {}); module('a ping', function () {}); module('an unknown type of message', function () {}); }); }); ================================================ FILE: client/web/addons/networking/tests/unit/services/messages/utils/-encryption-test.ts ================================================ // TODO: /* eslint-disable @typescript-eslint/no-explicit-any */ import { module, test } from 'qunit'; import { generateAsymmetricKeys } from '@emberclear/crypto/workers/crypto/utils/nacl'; import { decryptFromSocket, encryptForSocket, } from '@emberclear/crypto/workers/crypto/utils/socket'; import { toHex } from '@emberclear/encoding/string'; import { build as toPayloadJson } from '@emberclear/networking/services/messages/-utils/builder'; import type { KeyPair } from '@emberclear/crypto'; module('Integration | Send/Receive Encryption', function (hooks) { let bob!: KeyPair; let alice!: KeyPair; hooks.beforeEach(async function () { const bobKeys = await generateAsymmetricKeys(); const aliceKeys = await generateAsymmetricKeys(); bob = { privateKey: bobKeys.privateKey, publicKey: bobKeys.publicKey, }; alice = { privateKey: aliceKeys.privateKey, publicKey: aliceKeys.publicKey, }; }); test('round-trip encrypt-decrypt should return the same message', async function (assert) { const message = { body: 'hi', } as any; const payload = toPayloadJson(message, alice as any); const encrypted = await encryptForSocket(payload, bob, alice); const fakeSocketMessage = { message: encrypted, uid: toHex(alice.publicKey) }; const decrypted = await decryptFromSocket(fakeSocketMessage, bob.privateKey); assert.equal(decrypted.message.body, 'hi'); }); }); ================================================ FILE: client/web/addons/networking/tsconfig.compiler-options.json ================================================ { // Alias to reduce the number of ../ in paths "extends": "../../config/tsconfig.compiler-options.json" } ================================================ FILE: client/web/addons/networking/tsconfig.json ================================================ { "files": [], "compilerOptions": { "composite": true }, "references": [ { "path": "addon" }, { "path": "addon-test-support" }, { "path": "tests" } ] } ================================================ FILE: client/web/addons/networking/types/overrides.d.ts ================================================ import '@emberclear/questionably-typed/overrides'; import 'ember-concurrency-decorators'; import 'ember-concurrency-async'; import 'ember-concurrency-ts/async'; import 'ember-concurrency-test-waiter'; import '@emberclear/networking/type-support'; ================================================ FILE: client/web/addons/networking/vendor/.gitkeep ================================================ ================================================ FILE: client/web/addons/prism/README.md ================================================ extract dynamic loading of prism.js. This will eliminate the need for ember-prism's configuration objects. Also, maybe submit a PR to them? ================================================ FILE: client/web/addons/test-helpers/.editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: client/web/addons/test-helpers/.ember-cli ================================================ { /** Ember CLI sends analytics information by default. The data is completely anonymous, but there are times when you might want to disable this behavior. Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false } ================================================ FILE: client/web/addons/test-helpers/.eslintignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/test-helpers/.eslintrc.js ================================================ const { configs } = require('@nullvoxpopuli/eslint-configs'); module.exports = configs.ember(); ================================================ FILE: client/web/addons/test-helpers/.gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /.env* /.pnp* /.sass-cache /connect.lock /coverage/ /libpeerconnection.log /npm-debug.log* /testem.log /yarn-error.log # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/test-helpers/.npmignore ================================================ # compiled output /dist/ /tmp/ # dependencies /bower_components/ # misc /.bowerrc /.editorconfig /.ember-cli /.env* /.eslintignore /.eslintrc.js /.git/ /.gitignore /.template-lintrc.js /.travis.yml /.watchmanconfig /bower.json /config/ember-try.js /CONTRIBUTING.md /ember-cli-build.js /testem.js /tests/ /yarn.lock .gitkeep # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/test-helpers/.template-lintrc.js ================================================ 'use strict'; module.exports = require('@emberclear/config/.template-lintrc'); ================================================ FILE: client/web/addons/test-helpers/.travis.yml ================================================ --- language: node_js node_js: # we recommend testing addons with the same minimum supported node version as Ember CLI # so that your addon works for all apps - "10" dist: xenial addons: chrome: stable cache: yarn: true env: global: # See https://git.io/vdao3 for details. - JOBS=1 branches: only: - master # npm version tags - /^v\d+\.\d+\.\d+/ jobs: fast_finish: true allow_failures: - env: EMBER_TRY_SCENARIO=ember-canary include: # runs linting and tests with current locked deps - stage: "Tests" name: "Tests" script: - yarn lint - yarn test:ember - stage: "Additional Tests" name: "Floating Dependencies" install: - yarn install --no-lockfile --non-interactive script: - yarn test:ember # we recommend new addons test the current and previous LTS # as well as latest stable release (bonus points to beta/canary) - env: EMBER_TRY_SCENARIO=ember-lts-3.16 - env: EMBER_TRY_SCENARIO=ember-lts-3.20 - env: EMBER_TRY_SCENARIO=ember-release - env: EMBER_TRY_SCENARIO=ember-beta - env: EMBER_TRY_SCENARIO=ember-canary - env: EMBER_TRY_SCENARIO=ember-default-with-jquery - env: EMBER_TRY_SCENARIO=ember-classic before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH=$HOME/.yarn/bin:$PATH script: - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO ================================================ FILE: client/web/addons/test-helpers/.watchmanconfig ================================================ { "ignore_dirs": ["tmp", "dist"] } ================================================ FILE: client/web/addons/test-helpers/CONTRIBUTING.md ================================================ # How To Contribute ## Installation * `git clone ` * `cd test-helpers` * `yarn install` ## Linting * `yarn lint:hbs` * `yarn lint:js` * `yarn lint:js --fix` ## Running tests * `ember test` – Runs the test suite on the current Ember version * `ember test --server` – Runs the test suite in "watch mode" * `ember try:each` – Runs the test suite against multiple Ember versions ## Running the dummy application * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). ================================================ FILE: client/web/addons/test-helpers/LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: client/web/addons/test-helpers/README.md ================================================ test-helpers ============================================================================== [Short description of the addon.] Compatibility ------------------------------------------------------------------------------ * Ember.js v3.16 or above * Ember CLI v2.13 or above * Node.js v10 or above Installation ------------------------------------------------------------------------------ ``` ember install test-helpers ``` Usage ------------------------------------------------------------------------------ [Longer description of how to use the addon in apps.] Contributing ------------------------------------------------------------------------------ See the [Contributing](CONTRIBUTING.md) guide for details. License ------------------------------------------------------------------------------ This project is licensed under the [MIT License](LICENSE.md). ================================================ FILE: client/web/addons/test-helpers/addon-test-support/-private/get-service.ts ================================================ import { getContext } from '@ember/test-helpers'; import type { Registry } from '@ember/service'; import type { TestContext } from 'ember-test-helpers'; export function getService(name: K): Registry[K] { const { owner } = getContext() as TestContext; const service = owner.lookup(`service:${name}`); return service as Registry[K]; } ================================================ FILE: client/web/addons/test-helpers/addon-test-support/-private/get-store.ts ================================================ import { getService } from './get-service'; import type StoreService from '@ember-data/store'; export function getStore(): StoreService { return getService('store'); } ================================================ FILE: client/web/addons/test-helpers/addon-test-support/-private/refresh.ts ================================================ import { currentURL, getContext, setupContext, teardownContext } from '@ember/test-helpers'; import { visit } from './visit'; export async function refresh(mocking: () => T | Promise) { const url = currentURL(); const ctx = getContext(); await teardownContext(ctx); await setupContext(ctx); await mocking(); await visit(url); } ================================================ FILE: client/web/addons/test-helpers/addon-test-support/-private/setup-router.ts ================================================ import { getContext } from '@ember/test-helpers'; import type { TestContext } from 'ember-test-helpers'; export function setupRouter(hooks: NestedHooks) { hooks.beforeEach(function () { let { owner } = getContext() as TestContext; // eslint-disable-next-line ember/no-private-routing-service owner.lookup('router:main').setupRouter(); }); } ================================================ FILE: client/web/addons/test-helpers/addon-test-support/-private/stub-service.ts ================================================ import Service from '@ember/service'; import { getContext } from '@ember/test-helpers'; import type { Registry } from '@ember/service'; import type { TestContext } from 'ember-test-helpers'; export const stubService = (name: keyof Registry, hash = {}) => { let stubbedService; // TODO: need to be able to use an extended service that uses services. :) if (hash instanceof Function) { stubbedService = hash; } else { stubbedService = Service.extend(hash); } let { owner } = getContext() as TestContext; let serviceName = `service:${name}`; owner.register(serviceName, stubbedService); }; export default stubService; ================================================ FILE: client/web/addons/test-helpers/addon-test-support/-private/visit.ts ================================================ import { visit as dangerousVisit } from '@ember/test-helpers'; export async function visit(url: string) { try { await dangerousVisit(url); } catch (e) { if (!e.message.includes('TransitionAborted')) { throw e; } } } ================================================ FILE: client/web/addons/test-helpers/addon-test-support/-private/wait-until.ts ================================================ /** * @ember/test-helpers' waitUntil does not take a Promise for the callback */ export async function waitUntil(func: () => Promise, timeoutMs = 500) { let interval: NodeJS.Timeout; const timeout = new Promise((_resolve, reject) => { const id = setTimeout(() => { clearTimeout(id); clearInterval(interval); reject(`Timed out after ${timeoutMs} ms.`); }, timeoutMs); }); let startTime = new Date(); return Promise.race([ new Promise((resolve, reject) => { let interval = setInterval(async () => { if (new Date().getTime() - startTime.getTime() > 500) { clearInterval(interval); reject(`Timed out after ${timeoutMs}`); } let result = false; try { result = await func(); } catch (e) { // ignored } if (result) { clearInterval(interval); resolve(undefined); } }, 10); }), timeout, ]); } ================================================ FILE: client/web/addons/test-helpers/addon-test-support/index.ts ================================================ export { getService } from './-private/get-service'; export { getStore } from './-private/get-store'; export { refresh } from './-private/refresh'; export { setupRouter } from './-private/setup-router'; export { stubService } from './-private/stub-service'; export { visit } from './-private/visit'; export { waitUntil } from './-private/wait-until'; ================================================ FILE: client/web/addons/test-helpers/addon-test-support/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations/test-support", "paths": { "@emberclear/test-helpers": ["../declarations"], "@emberclear/test-helpers/*": ["../declarations/*"], "@emberclear/test-helpers/test-support": ["."], "@emberclear/test-helpers/test-support/*": ["./*"] } }, "references": [ /* { "path": "../addon" } */ ] } ================================================ FILE: client/web/addons/test-helpers/app/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/config/ember-try.js ================================================ 'use strict'; const getChannelURL = require('ember-source-channel-url'); module.exports = async function () { return { useYarn: true, scenarios: [ { name: 'ember-lts-3.16', npm: { devDependencies: { 'ember-source': '~3.16.0', }, }, }, { name: 'ember-lts-3.20', npm: { devDependencies: { 'ember-source': '~3.20.5', }, }, }, { name: 'ember-release', npm: { devDependencies: { 'ember-source': await getChannelURL('release'), }, }, }, { name: 'ember-beta', npm: { devDependencies: { 'ember-source': await getChannelURL('beta'), }, }, }, { name: 'ember-canary', npm: { devDependencies: { 'ember-source': await getChannelURL('canary'), }, }, }, { name: 'ember-default-with-jquery', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true, }), }, npm: { devDependencies: { '@ember/jquery': '^1.1.0', }, }, }, { name: 'ember-classic', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'application-template-wrapper': true, 'default-async-observers': false, 'template-only-glimmer-components': false, }), }, npm: { ember: { edition: 'classic', }, }, }, ], }; }; ================================================ FILE: client/web/addons/test-helpers/config/environment.js ================================================ 'use strict'; module.exports = function (/* environment, appConfig */) { return {}; }; ================================================ FILE: client/web/addons/test-helpers/ember-cli-build.js ================================================ 'use strict'; const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function (defaults) { let app = new EmberAddon(defaults, { // Add options here 'ember-cli-babel': { enableTypeScriptTransform: true, }, }); /* This build file specifies the options for the dummy test app of this addon, located in `/tests/dummy` This build file does *not* influence how the addon or the app using it behave. You most likely want to be modifying `./index.js` or app's build file */ return app.toTree(); }; ================================================ FILE: client/web/addons/test-helpers/index.js ================================================ 'use strict'; module.exports = { name: require('./package').name, // override isDevelopingAddon() { return true; }, options: { 'ember-cli-babel': { enableTypeScriptTransform: true, }, }, }; ================================================ FILE: client/web/addons/test-helpers/package.json ================================================ { "name": "@emberclear/test-helpers", "version": "0.0.0", "description": "The default blueprint for ember-cli addons.", "keywords": [ "ember-addon" ], "repository": { "url": "https://github.com/NullVoxPopuli/emberclear", "directory": "client/web/addons/tracked-local-storage" }, "license": "GPL-3.0", "author": "NullVoxPopuli", "directories": { "doc": "doc", "test": "tests" }, "scripts": { "build": "ember build --environment=production", "lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*", "lint:hbs": "ember-template-lint .", "lint:js": "eslint .", "start": "ember serve", "test": "ember test", "test:try-one": "ember try:one", "test:ember-compatibility": "ember try:each" }, "dependencies": { "ember-cli-babel": "7.26.6", "ember-cli-htmlbars": "5.7.1" }, "devDependencies": { "@ember/optional-features": "^2.0.0", "@emberclear/config": "*", "@emberclear/questionably-typed": "*", "@glimmer/component": "1.0.4", "@glimmer/tracking": "1.0.4", "@nullvoxpopuli/eslint-configs": "1.3.2", "@types/ember": "3.16.5", "@types/ember-data": "3.16.14", "@types/ember-data__model": "3.16.2", "@types/ember-data__store": "3.16.1", "@types/ember-qunit": "3.4.13", "@types/ember-resolver": "^5.0.10", "@types/ember__test-helpers": "2.0.0", "@types/qunit": "2.11.1", "@types/rsvp": "4.0.3", "broccoli-asset-rev": "^3.0.0", "ember-auto-import": "^1.11.3", "ember-cli": "3.26.1", "ember-cli-dependency-checker": "^3.2.0", "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "4.0.2", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", "ember-load-initializers": "2.1.2", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", "ember-resolver": "^8.0.2", "ember-source": "3.26.1", "ember-source-channel-url": "^3.0.0", "ember-try": "^1.4.0", "loader.js": "^4.7.0", "npm-run-all": "^4.1.5", "qunit-dom": "1.6.0", "typescript": "4.3.4" }, "engines": { "node": "14.17.1" }, "ember": { "edition": "octane" }, "ember-addon": { "configPath": "tests/dummy/config" }, "typesVersions": { "*": { "*": [ "declarations/*", "declarations/*/index" ] } }, "volta": { "node": "14.17.1", "yarn": "1.22.10" } } ================================================ FILE: client/web/addons/test-helpers/testem.js ================================================ 'use strict'; module.exports = require('@emberclear/config/testem'); ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/app.ts ================================================ import Application from '@ember/application'; import config from 'dummy/config/environment'; import loadInitializers from 'ember-load-initializers'; import Resolver from 'ember-resolver'; export default class App extends Application { modulePrefix = config.modulePrefix; podModulePrefix = config.podModulePrefix; Resolver = Resolver; } loadInitializers(App, config.modulePrefix); ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/components/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/config/environment.d.ts ================================================ export default config; /** * Type declarations for * import config from './config/environment' * * For now these need to be managed by the developer * since different ember addons can materialize new entries. */ declare const config: { environment: 'production' | 'test' | 'development'; modulePrefix: string; podModulePrefix: string; locationType: string; rootURL: string; host: string; APP: Record; }; ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/controllers/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/index.html ================================================ Dummy {{content-for "head"}} {{content-for "head-footer"}} {{content-for "body"}} {{content-for "body-footer"}} ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/models/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/router.js ================================================ import EmberRouter from '@ember/routing/router'; import config from 'dummy/config/environment'; export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; } Router.map(function () {}); ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/routes/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/styles/app.css ================================================ ================================================ FILE: client/web/addons/test-helpers/tests/dummy/app/templates/application.hbs ================================================ {{outlet}} ================================================ FILE: client/web/addons/test-helpers/tests/dummy/config/ember-cli-update.json ================================================ { "schemaVersion": "1.0.0", "packages": [ { "name": "ember-cli", "version": "3.23.0", "blueprints": [ { "name": "addon", "outputRepo": "https://github.com/ember-cli/ember-addon-output", "codemodsSource": "ember-addon-codemods-manifest@1", "isBaseBlueprint": true, "options": [ "--yarn" ] } ] } ] } ================================================ FILE: client/web/addons/test-helpers/tests/dummy/config/environment.js ================================================ 'use strict'; module.exports = function (environment) { let ENV = { modulePrefix: 'dummy', environment, rootURL: '/', locationType: 'auto', EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true }, EXTEND_PROTOTYPES: { // Prevent Ember Data from overriding Date.parse. Date: false, }, }, APP: { // Here you can pass flags/options to your application instance // when it is created }, }; if (environment === 'development') { // ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_ACTIVE_GENERATION = true; // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; } if (environment === 'test') { // Testem prefers this... ENV.locationType = 'none'; // keep test console output quieter ENV.APP.LOG_ACTIVE_GENERATION = false; ENV.APP.LOG_VIEW_LOOKUPS = false; ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; } if (environment === 'production') { // here you can enable a production-specific feature } return ENV; }; ================================================ FILE: client/web/addons/test-helpers/tests/dummy/config/optional-features.json ================================================ { "application-template-wrapper": false, "default-async-observers": true, "jquery-integration": false, "template-only-glimmer-components": true } ================================================ FILE: client/web/addons/test-helpers/tests/dummy/config/targets.js ================================================ 'use strict'; const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; const isCI = Boolean(process.env.CI); const isProduction = process.env.EMBER_ENV === 'production'; if (isCI || isProduction) { browsers.push('ie 11'); } module.exports = { browsers, }; ================================================ FILE: client/web/addons/test-helpers/tests/dummy/public/robots.txt ================================================ # http://www.robotstxt.org User-agent: * Disallow: ================================================ FILE: client/web/addons/test-helpers/tests/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/tests/index.html ================================================ Dummy Tests {{content-for "head"}} {{content-for "test-head"}} {{content-for "head-footer"}} {{content-for "test-head-footer"}} {{content-for "body"}} {{content-for "test-body"}} {{content-for "body-footer"}} {{content-for "test-body-footer"}} ================================================ FILE: client/web/addons/test-helpers/tests/integration/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/tests/test-helper.js ================================================ import { setApplication } from '@ember/test-helpers'; import { start } from 'ember-qunit'; import Application from 'dummy/app'; import config from 'dummy/config/environment'; setApplication(Application.create(config.APP)); start(); ================================================ FILE: client/web/addons/test-helpers/tests/unit/.gitkeep ================================================ ================================================ FILE: client/web/addons/test-helpers/tsconfig.compiler-options.json ================================================ { // Alias to reduce the number of ../ in paths "extends": "../../config/tsconfig.compiler-options.json" } ================================================ FILE: client/web/addons/test-helpers/tsconfig.json ================================================ { "files": [], "compilerOptions": { "composite": true }, "references": [ /* { "path": "addon" }, */ { "path": "addon-test-support" } /* { "path": "tests" } */ ] } ================================================ FILE: client/web/addons/test-helpers/types/overrides.d.ts ================================================ import '@emberclear/questionably-typed/overrides'; ================================================ FILE: client/web/addons/test-helpers/vendor/.gitkeep ================================================ ================================================ FILE: client/web/addons/tracked-local-storage/.editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: client/web/addons/tracked-local-storage/.ember-cli ================================================ { /** Ember CLI sends analytics information by default. The data is completely anonymous, but there are times when you might want to disable this behavior. Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false } ================================================ FILE: client/web/addons/tracked-local-storage/.eslintignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/tracked-local-storage/.eslintrc.js ================================================ const { configs } = require('@nullvoxpopuli/eslint-configs'); module.exports = configs.ember(); ================================================ FILE: client/web/addons/tracked-local-storage/.gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /.env* /.pnp* /.sass-cache /connect.lock /coverage/ /libpeerconnection.log /npm-debug.log* /testem.log /yarn-error.log # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/tracked-local-storage/.npmignore ================================================ # compiled output /dist/ /tmp/ # dependencies /bower_components/ # misc /.bowerrc /.editorconfig /.ember-cli /.env* /.eslintignore /.eslintrc.js /.git/ /.gitignore /.template-lintrc.js /.travis.yml /.watchmanconfig /bower.json /config/ember-try.js /CONTRIBUTING.md /ember-cli-build.js /testem.js /tests/ /yarn.lock .gitkeep # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/tracked-local-storage/.template-lintrc.js ================================================ 'use strict'; module.exports = require('@emberclear/config/.template-lintrc'); ================================================ FILE: client/web/addons/tracked-local-storage/.watchmanconfig ================================================ { "ignore_dirs": ["tmp", "dist"] } ================================================ FILE: client/web/addons/tracked-local-storage/CONTRIBUTING.md ================================================ # How To Contribute ## Installation * `git clone ` * `cd tracked-local-storage` * `yarn install` ## Linting * `yarn lint:hbs` * `yarn lint:js` * `yarn lint:js --fix` ## Running tests * `ember test` – Runs the test suite on the current Ember version * `ember test --server` – Runs the test suite in "watch mode" * `ember try:each` – Runs the test suite against multiple Ember versions ## Running the dummy application * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). ================================================ FILE: client/web/addons/tracked-local-storage/LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: client/web/addons/tracked-local-storage/README.md ================================================ tracked-local-storage ============================================================================== [Short description of the addon.] Compatibility ------------------------------------------------------------------------------ * Ember.js v3.16 or above * Ember CLI v2.13 or above * Node.js v10 or above Installation ------------------------------------------------------------------------------ ``` ember install tracked-local-storage ``` Usage ------------------------------------------------------------------------------ [Longer description of how to use the addon in apps.] Contributing ------------------------------------------------------------------------------ See the [Contributing](CONTRIBUTING.md) guide for details. License ------------------------------------------------------------------------------ This project is licensed under the [MIT License](LICENSE.md). ================================================ FILE: client/web/addons/tracked-local-storage/addon/index.ts ================================================ import { assert } from '@ember/debug'; import { get, notifyPropertyChange } from '@ember/object'; interface WhyCantTSGetDecoratorsRight { initializer?: () => InitializedValue; } /** * Kinda Pre-stage 2 decorator. * * Will need to update when decorators hit stage 3 * */ // TODO: figure out a better default type for Klass // eslint-disable-next-line @typescript-eslint/ban-types export function inLocalStorage( target: Klass, propertyKey: keyof Klass, // descriptor is undefined for properties // it's only available on methods and such descriptor?: WhyCantTSGetDecoratorsRight ): void /* TS says the return value is ignored... idk if I believe it */ { let targetName = target.constructor.name; assert(`@inLocalStorage is only usable on class properties`, descriptor); let { initializer } = descriptor; const newDescriptor: PropertyDescriptor = { configurable: true, enumerable: true, get: function (this: Klass): T { let key = `${targetName}-${propertyKey}`; const lsValue = localStorage.getItem(key); const value = (lsValue && JSON.parse(lsValue))?.value || initializer?.(); // Entagle with tracking system // This is a bit of a hack and the returned value doesn't matter // eslint-disable-next-line @typescript-eslint/no-explicit-any get(this, key as any); return value; }, set: function (value: T) { const key = `${targetName}-${propertyKey}`; const lsValue = JSON.stringify({ value }); localStorage.setItem(key, lsValue); // this is required to dirty the change tracking system notifyPropertyChange(this, key); }, }; // I think TypeScript is wrong when it comes to decorators... // eslint-disable-next-line @typescript-eslint/no-explicit-any return newDescriptor as any; } ================================================ FILE: client/web/addons/tracked-local-storage/addon/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", "declarationDir": "../declarations", "paths": { "ember-tracked-local-storage": ["."], "ember-tracked-local-storage/*": ["./*"], "*": ["../types/*"] } }, "include": [".", "../types"], "references": [ ] } ================================================ FILE: client/web/addons/tracked-local-storage/config/ember-try.js ================================================ 'use strict'; const getChannelURL = require('ember-source-channel-url'); module.exports = async function () { return { useYarn: true, scenarios: [ { name: 'ember-lts-3.16', npm: { devDependencies: { 'ember-source': '~3.16.0', }, }, }, { name: 'ember-lts-3.20', npm: { devDependencies: { 'ember-source': '~3.20.5', }, }, }, { name: 'ember-release', npm: { devDependencies: { 'ember-source': await getChannelURL('release'), }, }, }, { name: 'ember-beta', npm: { devDependencies: { 'ember-source': await getChannelURL('beta'), }, }, }, { name: 'ember-canary', npm: { devDependencies: { 'ember-source': await getChannelURL('canary'), }, }, }, ], }; }; ================================================ FILE: client/web/addons/tracked-local-storage/config/environment.js ================================================ 'use strict'; module.exports = function (/* environment, appConfig */) { return {}; }; ================================================ FILE: client/web/addons/tracked-local-storage/ember-cli-build.js ================================================ 'use strict'; const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function (defaults) { let app = new EmberAddon(defaults, { 'ember-cli-babel': { enableTypeScriptTransform: true, }, }); /* This build file specifies the options for the dummy test app of this addon, located in `/tests/dummy` This build file does *not* influence how the addon or the app using it behave. You most likely want to be modifying `./index.js` or app's build file */ return app.toTree(); }; ================================================ FILE: client/web/addons/tracked-local-storage/index.js ================================================ 'use strict'; module.exports = { name: require('./package').name, // override isDevelopingAddon() { return true; }, options: { 'ember-cli-babel': { enableTypeScriptTransform: true, }, }, }; ================================================ FILE: client/web/addons/tracked-local-storage/package.json ================================================ { "name": "ember-tracked-local-storage", "version": "0.0.0", "description": "stores the return value of getters / properties in local-storage", "keywords": [ "ember-addon" ], "repository": { "url": "https://github.com/NullVoxPopuli/emberclear", "directory": "client/web/addons/tracked-local-storage" }, "license": "GPL-3.0", "author": "NullVoxPopuli", "directories": { "doc": "doc", "test": "tests" }, "scripts": { "build": "ember build --environment=production", "lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*", "lint:hbs": "ember-template-lint .", "lint:js": "eslint . --ext js,ts", "start": "ember serve", "test": "ember test", "test:try-one": "ember try:one", "test:ember-compatibility": "ember try:each" }, "dependencies": { "ember-cli-babel": "7.26.6", "ember-cli-htmlbars": "5.7.1" }, "devDependencies": { "@ember/optional-features": "^2.0.0", "@emberclear/config": "*", "@glimmer/component": "1.0.4", "@nullvoxpopuli/eslint-configs": "1.3.2", "@glimmer/tracking": "1.0.4", "@types/ember": "3.16.5", "@types/ember-qunit": "3.4.13", "@types/ember-resolver": "^5.0.10", "@types/ember__test-helpers": "2.0.0", "@types/qunit": "2.11.1", "@types/rsvp": "4.0.3", "broccoli-asset-rev": "^3.0.0", "ember-auto-import": "^1.11.3", "ember-cli": "3.26.1", "ember-cli-dependency-checker": "^3.2.0", "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "4.0.2", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", "ember-load-initializers": "2.1.2", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", "ember-resolver": "^8.0.2", "ember-source": "3.26.1", "ember-source-channel-url": "^3.0.0", "ember-try": "^1.4.0", "loader.js": "^4.7.0", "npm-run-all": "^4.1.5", "qunit-dom": "1.6.0", "typescript": "4.3.4" }, "engines": { "node": "14.17.1" }, "ember": { "edition": "octane" }, "ember-addon": { "configPath": "tests/dummy/config" }, "typesVersions": { "*": { "*": [ "declarations/*", "declarations/*/index" ] } }, "volta": { "node": "14.17.1", "yarn": "1.22.10" } } ================================================ FILE: client/web/addons/tracked-local-storage/testem.js ================================================ 'use strict'; module.exports = require('@emberclear/config/testem'); ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/app.ts ================================================ import Application from '@ember/application'; import config from 'dummy/config/environment'; import loadInitializers from 'ember-load-initializers'; import Resolver from 'ember-resolver'; export default class App extends Application { modulePrefix = config.modulePrefix; podModulePrefix = config.podModulePrefix; Resolver = Resolver; } loadInitializers(App, config.modulePrefix); ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/components/.gitkeep ================================================ ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/config/environment.d.ts ================================================ export default config; /** * Type declarations for * import config from './config/environment' * * For now these need to be managed by the developer * since different ember addons can materialize new entries. */ declare const config: { environment: 'production' | 'test' | 'development'; modulePrefix: string; podModulePrefix: string; locationType: string; rootURL: string; APP: Record; }; ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/controllers/.gitkeep ================================================ ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/helpers/.gitkeep ================================================ ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/index.html ================================================ Dummy {{content-for "head"}} {{content-for "head-footer"}} {{content-for "body"}} {{content-for "body-footer"}} ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/models/.gitkeep ================================================ ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/router.js ================================================ import EmberRouter from '@ember/routing/router'; import config from 'dummy/config/environment'; export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; } Router.map(function () {}); ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/routes/.gitkeep ================================================ ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/styles/app.css ================================================ ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/app/templates/application.hbs ================================================ {{outlet}} ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/config/ember-cli-update.json ================================================ { "schemaVersion": "1.0.0", "packages": [ { "name": "ember-cli", "version": "3.23.0", "blueprints": [ { "name": "addon", "outputRepo": "https://github.com/ember-cli/ember-addon-output", "codemodsSource": "ember-addon-codemods-manifest@1", "isBaseBlueprint": true, "options": [ "--yarn" ] } ] } ] } ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/config/environment.js ================================================ 'use strict'; module.exports = function (environment) { let ENV = { modulePrefix: 'dummy', environment, rootURL: '/', locationType: 'auto', EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true }, EXTEND_PROTOTYPES: { // Prevent Ember Data from overriding Date.parse. Date: false, }, }, APP: { // Here you can pass flags/options to your application instance // when it is created }, }; if (environment === 'development') { // ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_ACTIVE_GENERATION = true; // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; } if (environment === 'test') { // Testem prefers this... ENV.locationType = 'none'; // keep test console output quieter ENV.APP.LOG_ACTIVE_GENERATION = false; ENV.APP.LOG_VIEW_LOOKUPS = false; ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; } if (environment === 'production') { // here you can enable a production-specific feature } return ENV; }; ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/config/optional-features.json ================================================ { "application-template-wrapper": false, "default-async-observers": true, "jquery-integration": false, "template-only-glimmer-components": true } ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/config/targets.js ================================================ 'use strict'; const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; const isCI = Boolean(process.env.CI); const isProduction = process.env.EMBER_ENV === 'production'; if (isCI || isProduction) { browsers.push('ie 11'); } module.exports = { browsers, }; ================================================ FILE: client/web/addons/tracked-local-storage/tests/dummy/public/robots.txt ================================================ # http://www.robotstxt.org User-agent: * Disallow: ================================================ FILE: client/web/addons/tracked-local-storage/tests/index.html ================================================ Dummy Tests {{content-for "head"}} {{content-for "test-head"}} {{content-for "head-footer"}} {{content-for "test-head-footer"}} {{content-for "body"}} {{content-for "test-body"}} {{content-for "body-footer"}} {{content-for "test-body-footer"}} ================================================ FILE: client/web/addons/tracked-local-storage/tests/test-helper.ts ================================================ import { setApplication } from '@ember/test-helpers'; import { start } from 'ember-qunit'; import Application from 'dummy/app'; import config from 'dummy/config/environment'; setApplication(Application.create(config.APP)); start(); ================================================ FILE: client/web/addons/tracked-local-storage/tests/tsconfig.json ================================================ { "extends": "../tsconfig.compiler-options.json", "compilerOptions": { "baseUrl": ".", // Question: what's the best place for test and dummy declarations to go? They // aren't actually needed for anything other than to satisfy the requirements // for a composite build. "declarationDir": "./dummy/declarations", "paths": { "dummy/tests/*": ["./*"], "dummy/*": ["./dummy/app/*"], "ember-tracked-local-storage": ["../declarations"], "ember-tracked-local-storage/*": ["../declarations/*"], "*": ["../types/*"] } }, "include": [ ".", "../types" ], "references": [ { "path": "../addon" } /* { "path": "../addon-test-support" } */ ] } ================================================ FILE: client/web/addons/tracked-local-storage/tests/unit/in-local-storage-test.ts ================================================ import { module, test } from 'qunit'; import { inLocalStorage } from 'ember-tracked-local-storage'; /** * When using @inLocalStorage, * anything stored in there from this decorator is considered private. */ module('Unit | inLocalStorage', function (hooks) { hooks.afterEach(function () { localStorage.clear(); }); test('starts with no initial value', function (assert) { class Foo { @inLocalStorage bar?: number; } let foo = new Foo(); assert.equal(localStorage.getItem('Foo-bar'), null); foo.bar = 2; assert.equal(localStorage.getItem('Foo-bar'), storedValueOf(2)); }); test('starts with a pre-existing value', function (assert) { localStorage.setItem('Foo-bar', storedValueOf(2)); class Foo { @inLocalStorage bar?: number; } let foo = new Foo(); assert.equal(foo.bar, 2); foo.bar = 3; assert.equal(localStorage.getItem('Foo-bar'), storedValueOf(3)); assert.equal(foo.bar, 3); }); test('handles any data type that is serializable via JSON', function (assert) { class Foo { @inLocalStorage num?: number; @inLocalStorage str?: string; @inLocalStorage bool?: boolean; @inLocalStorage numberArray?: number[]; @inLocalStorage stringArray?: string[]; @inLocalStorage complex?: unknown; } let foo: Foo | undefined; foo = new Foo(); let fixtureData = { num: 1, str: 'hi', bool: true, numberArray: [1, 3], stringArray: ['hello', 'there'], complex: { arr: [1, 2, 'string', true, false], nested: { obj: true, }, }, }; foo.num = fixtureData.num; foo.str = fixtureData.str; foo.bool = fixtureData.bool; foo.numberArray = fixtureData.numberArray; foo.stringArray = fixtureData.stringArray; foo.complex = fixtureData.complex; assert.equal(localStorage.getItem('Foo-num'), storedValueOf(fixtureData.num)); assert.equal(localStorage.getItem('Foo-str'), storedValueOf(fixtureData.str)); assert.equal(localStorage.getItem('Foo-bool'), storedValueOf(fixtureData.bool)); assert.equal(localStorage.getItem('Foo-numberArray'), storedValueOf(fixtureData.numberArray)); assert.equal(localStorage.getItem('Foo-stringArray'), storedValueOf(fixtureData.stringArray)); assert.equal(localStorage.getItem('Foo-complex'), storedValueOf(fixtureData.complex)); // Get a new instance so that the loading logic needs to apply foo = undefined; foo = new Foo(); assert.equal(foo.num, fixtureData.num); assert.equal(foo.str, fixtureData.str); assert.equal(foo.bool, fixtureData.bool); assert.deepEqual(foo.numberArray, fixtureData.numberArray); assert.deepEqual(foo.stringArray, fixtureData.stringArray); assert.deepEqual(foo.complex, fixtureData.complex); }); }); function storedValueOf(value: unknown) { return JSON.stringify({ value }); } ================================================ FILE: client/web/addons/tracked-local-storage/tsconfig.compiler-options.json ================================================ { // Alias to reduce the number of ../ in paths "extends": "../../config/tsconfig.compiler-options.json" } ================================================ FILE: client/web/addons/tracked-local-storage/tsconfig.json ================================================ { "files": [], "compilerOptions": { "composite": true }, "references": [ { "path": "addon" }, /* { "path": "addon-test-support" }, */ { "path": "tests" } ] } ================================================ FILE: client/web/addons/tracked-local-storage/vendor/.gitkeep ================================================ ================================================ FILE: client/web/addons/tsconfig.json ================================================ { "$schema": "http://json.schemastore.org/tsconfig", "files": [], "references": [ { "path": "./crypto" }, { "path": "./encoding" }, { "path": "./local-account" }, { "path": "./networking" }, { "path": "./test-helpers" }, { "path": "./tracked-local-storage" }, { "path": "./ui" } ] } ================================================ FILE: client/web/addons/ui/.editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: client/web/addons/ui/.ember-cli ================================================ { /** Ember CLI sends analytics information by default. The data is completely anonymous, but there are times when you might want to disable this behavior. Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false, "liveReload": true, "usePods": false } ================================================ FILE: client/web/addons/ui/.eslintignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/ui/.eslintrc.js ================================================ const { configs } = require('@nullvoxpopuli/eslint-configs'); module.exports = configs.ember(); ================================================ FILE: client/web/addons/ui/.gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/ /tmp/ # TypeScript output declarations/ tsconfig.tsbuildinfo # dependencies /bower_components/ /node_modules/ # misc /.env* /.pnp* /.sass-cache /connect.lock /coverage/ /libpeerconnection.log /npm-debug.log* /testem.log /yarn-error.log # ember-try /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try ================================================ FILE: client/web/addons/ui/.template-lintrc.js ================================================ 'use strict'; module.exports = require('@emberclear/config/.template-lintrc'); ================================================ FILE: client/web/addons/ui/README.md ================================================ ui ============================================================================== [Short description of the addon.] Compatibility ------------------------------------------------------------------------------ * Ember.js v3.12 or above * Ember CLI v2.13 or above * Node.js v10 or above Installation ------------------------------------------------------------------------------ ``` ember install ui ``` Usage ------------------------------------------------------------------------------ [Longer description of how to use the addon in apps.] Contributing ------------------------------------------------------------------------------ See the [Contributing](CONTRIBUTING.md) guide for details. License ------------------------------------------------------------------------------ This project is licensed under the [MIT License](LICENSE.md). ================================================ FILE: client/web/addons/ui/addon/components/backdrop.hbs ================================================
================================================ FILE: client/web/addons/ui/addon/components/collapsible/icon.hbs ================================================ {{#if @isOpen}} {{else}} {{/if}} ================================================ FILE: client/web/addons/ui/addon/components/collapsible/index.ts ================================================ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { setComponentTemplate } from '@ember/component'; import { action } from '@ember/object'; import { hbs } from 'ember-cli-htmlbars'; class Collapsible extends Component { @tracked isOpen = true; @action toggle() { this.isOpen = !this.isOpen; } } export default setComponentTemplate( hbs` {{yield this.isOpen this.toggle (component 'collapsible/icon') }} `, Collapsible ); ================================================ FILE: client/web/addons/ui/addon/components/dropdown/index.hbs ================================================