diff --git a/.babelrc b/.babelrc index 836f6ffe17..71efb0f332 100755 --- a/.babelrc +++ b/.babelrc @@ -7,6 +7,7 @@ "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }], + ["@babel/plugin-proposal-private-property-in-object", { "loose": true }], [ "@babel/plugin-proposal-private-methods", { diff --git a/.eslintrc b/.eslintrc index edd188ead2..f6ed9a7c95 100755 --- a/.eslintrc +++ b/.eslintrc @@ -1,5 +1,5 @@ { - "parser": "babel-eslint", + "parser": "@babel/eslint-parser", "parserOptions": { "ecmaFeatures": { "legacyDecorators": true @@ -12,12 +12,14 @@ "prettier", "prettier/babel", "prettier/flowtype", - "prettier/react" + "prettier/react", + "plugin:jest/recommended" ], "env": { "browser": true, "mocha": true, - "node": true + "node": true, + "jest": true }, "rules": { "class-methods-use-this": 0, @@ -77,7 +79,8 @@ "flowtype", "import", "promise", - "react" + "react", + "jest" ], "globals": { "API": true, diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 159c681a70..de92566601 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,17 +1,35 @@ -This PR CHANGES. + + +This PR ... ## Todos -- [ ] Todo + -## Screenshots +- [ ] TODO -- Screenshot +## Screenshots ---- + ## Testing Checklist + + + - [Slack QA thread](https://input-output-rnd.slack.com/messages/GGKFXSKC6) - [ ] Test @@ -20,24 +38,18 @@ This PR CHANGES. ## Review Checklist ### Basics - -- [ ] PR has been assigned and has appropriate labels (`feature`/`bug`/`chore`, `release-x.x.x`) +- [ ] PR assigned to the PR author(s) +- [ ] `input-output-hk/daedalus-dev` and `input-output-hk/daedalus-qa` assigned as PR reviewers +- [ ] If there are UI changes, Alexander Rukin assigned as an additional reviewer +- [ ] PR has appropriate labels (`release-vNext`, `feature`/`bug`/`chore`, `WIP`) +- [ ] PR link is added to a Jira ticket, ticket moved to In Review - [ ] PR is updated to the most recent version of the target branch (and there are no conflicts) - [ ] PR has a good description that summarizes all changes -- [ ] PR has default-sized Daedalus window screenshots or animated GIFs of important UI changes: - - [ ] In English - - [ ] In Japanese +- [ ] PR contains screenshots (in case of UI changes) - [ ] CHANGELOG entry has been added to the top of the appropriate section (*Features*, *Fixes*, *Chores*) and is linked to the correct PR on GitHub -- [ ] Automated tests: All acceptance and unit tests are passing (`yarn test`) -- [ ] Manual tests (minimum tests should cover newly added feature/fix): App works correctly in *development* build (`yarn dev`) -- [ ] Manual tests (minimum tests should cover newly added feature/fix): App works correctly in *production* build (`yarn package` / CI builds) -- [ ] There are no *flow* errors or warnings (`yarn flow:test`) -- [ ] There are no *lint* errors or warnings (`yarn lint`) -- [ ] There are no *prettier* errors or warnings (`yarn prettier:check`) - [ ] There are no missing translations (running `yarn manage:translations` produces no changes) - [ ] Text changes are proofread and approved (Jane Wild / Amy Reeve) - [ ] Japanese text changes are proofread and approved (Junko Oda) -- [ ] UI changes look good in all themes (Alexander Rukin) - [ ] Storybook works and no stories are broken (`yarn storybook`) - [ ] In case of dependency changes `yarn.lock` file is updated @@ -55,7 +67,4 @@ This PR CHANGES. - [ ] All existing Daedalus Testing scenarios are still up-to-date ### After Review -- [ ] Merge the PR -- [ ] Delete the source branch -- [ ] Move the ticket to `done` column on the YouTrack board - [ ] Update Slack QA thread by marking it with a green checkmark diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 0000000000..d4dd52920c --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,27 @@ +name: 'Chromatic' +on: push +jobs: + chromatic-deployment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" + - name: Install dependencies + run: yarn + - name: Publish to Chromatic + if: github.ref != 'refs/heads/develop' + uses: chromaui/action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + exitZeroOnChanges: true + - name: Publish to Chromatic and auto accept changes + if: github.ref == 'refs/heads/develop' + uses: chromaui/action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + autoAcceptChanges: true diff --git a/.github/workflows/run_tests_on_pr.yml b/.github/workflows/run_tests_on_pr.yml new file mode 100644 index 0000000000..3ce7787de8 --- /dev/null +++ b/.github/workflows/run_tests_on_pr.yml @@ -0,0 +1,18 @@ +name: Run Tests for Pull Requests +on: + pull_request: + branches: + - develop + - master +jobs: + tests: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: "12" + - run: yarn install + - run: yarn test:jest diff --git a/.gitignore b/.gitignore index b3e42e3d38..b43c8bb717 100755 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,10 @@ mainnet-genesis-dryrun-with-stakeholders.json # nix-build results result* +!result*.js +!result*.jsx +!result*.ts +!result*.tsx # Npm package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index c03e8bda82..6fb51f4bd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,60 @@ # Changelog +## 4.8.0 + +### Features + +- Added dynamic RTS flags setting ([PR 2758](https://github.com/input-output-hk/daedalus/pull/2758/files)) +- Improved UI/UX of RTS flags settings ([PR 2842](https://github.com/input-output-hk/daedalus/pull/2842), [PR 2846](https://github.com/input-output-hk/daedalus/pull/2846)) +- Updated messages about Cardano node sync on the initial screen ([PR 2827](https://github.com/input-output-hk/daedalus/pull/2827), [PR 2831](https://github.com/input-output-hk/daedalus/pull/2831)) + +### Chores + +- Updated check-disk-space version ([PR 2845](https://github.com/input-output-hk/daedalus/pull/2845)) +- Updated CWB and Cardano Node ([PR 2822](https://github.com/input-output-hk/daedalus/pull/2822)) + +### Fixes + +- Fixed blockain verification progress text ([PR 2840](https://github.com/input-output-hk/daedalus/pull/2840)) + +## 4.7.0 + +### Features + +- Updated Catalyst dates ([PR 2812](https://github.com/input-output-hk/daedalus/pull/2812)) + +### Fixes + +- Fixed immediate language updates of application top menu bar ([PR 2813](https://github.com/input-output-hk/daedalus/pull/2813)) +- Fixed receiver address validation by disallowing rewards addresses ([PR 2781](https://github.com/input-output-hk/daedalus/pull/2781)) + +### Chores + +- Integrated Chromatic for visual regression testing ([PR 2776](https://github.com/input-output-hk/daedalus/pull/2776)) +- Updated `trezor-connect` dependency to version `8.2.4` ([PR 2726](https://github.com/input-output-hk/daedalus/pull/2726)) +- Updated vulnerable dependencies ([PR 2769](https://github.com/input-output-hk/daedalus/pull/2769)) +- Updated CWB and Cardano Node ([PR 2799](https://github.com/input-output-hk/daedalus/pull/2799)) + +## 4.6.0 + +### Features + +- Implement catalyst state snapshot phase ([PR 2771](https://github.com/input-output-hk/daedalus/pull/2771)) +- Implemented "discreet mode" ([PR 2723](https://github.com/input-output-hk/daedalus/pull/2723), [PR 2724](https://github.com/input-output-hk/daedalus/pull/2724), [PR 2725](https://github.com/input-output-hk/daedalus/pull/2725), [PR 2742](https://github.com/input-output-hk/daedalus/pull/2742), [PR 2740](https://github.com/input-output-hk/daedalus/pull/2740), [PR 2756](https://github.com/input-output-hk/daedalus/pull/2756)) +- Updated slider component to only execute onAfterChange if slider had moved ([PR 2766](https://github.com/input-output-hk/daedalus/pull/2766)) + +### Fixes + +- Fixed app update for specific platform ([PR 2759](https://github.com/input-output-hk/daedalus/pull/2759)) +- Fixed checkbox tick offset ([PR 2751](https://github.com/input-output-hk/daedalus/pull/2751)) + +### Chores + +- Improve startup and shutdown messages ([PR 2770](https://github.com/input-output-hk/daedalus/pull/2770)) +- Updated `cardano-wallet` to version `2021-11-11` ([PR 2765](https://github.com/input-output-hk/daedalus/pull/2765)) +- Added jest library for unit testing ([PR 2633](https://github.com/input-output-hk/daedalus/pull/2633)) +- Updated `cardano-launcher` to version `0.20211105.1` + ## 4.5.2 ### Fixes diff --git a/README.md b/README.md index e1376c2886..caddda18f8 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,25 @@ Daedalus - Cryptocurrency Wallet ``` and then add the following lines: ``` + build-users-group = nixbld + + max-jobs = auto + cores = 0 + sandbox = false + + require-sigs = true + trusted-users = root + allowed-users = * + substituters = https://hydra.iohk.io https://cache.nixos.org/ trusted-substituters = trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - max-jobs = 2 # run at most two builds at once - cores = 0 # the builder will use all available CPU cores - extra-sandbox-paths = /System/Library/Frameworks + extra-sandbox-paths = /System/Library/Frameworks /System/Library/PrivateFrameworks /usr/lib + + # If you are running on a Mac with M1 chip please uncomment 'system' setting to enforce running on Rosetta2 + # system = x86_64-darwin ``` + 3. Run `nix-shell` with correct list of arguments or by using existing `package.json` scripts to load a shell with all the correct versions of all the required dependencies for development. **Notes:** diff --git a/default.nix b/default.nix index 2bfa1e87c7..e7b780dad4 100644 --- a/default.nix +++ b/default.nix @@ -30,7 +30,7 @@ let }); }; }; - pkgs = localLib.iohkNix.getPkgsDefault { inherit system config; }; + pkgs = import sources.nixpkgs { inherit system config; }; pkgsNative = localLib.iohkNix.getPkgsDefault {}; sources = localLib.sources; walletPkgs = import "${sources.cardano-wallet}/nix" {}; @@ -51,6 +51,7 @@ let ostable.x86_64-darwin = "macos64"; packages = self: { inherit cluster pkgs version target nodeImplementation; + inherit (pkgs) hello cabal2nix; cardanoLib = localLib.iohkNix.cardanoLib; daedalus-bridge = self.bridgeTable.${nodeImplementation}; @@ -313,7 +314,7 @@ let }; rawapp-win64 = self.rawapp.override { win64 = true; }; source = builtins.filterSource localLib.cleanSourceFilter ./.; - yaml2json = pkgs.haskell.lib.disableCabalFlag pkgs.haskellPackages.yaml "no-exe"; + yaml2json = pkgs.haskell.lib.addExtraLibrary (pkgs.haskell.lib.disableCabalFlag pkgs.haskellPackages.yaml "no-exe") pkgs.haskellPackages.optparse-applicative; electron = pkgs.callPackage ./installers/nix/electron.nix {}; diff --git a/flow-typed/npm/jest_v27.x.x.js b/flow-typed/npm/jest_v27.x.x.js new file mode 100644 index 0000000000..ce41549fea --- /dev/null +++ b/flow-typed/npm/jest_v27.x.x.js @@ -0,0 +1,1222 @@ +// flow-typed signature: b87087aa1a754cdc00c0adcb865b9fd4 +// flow-typed version: 9bbc41691b/jest_v27.x.x/flow_>=v0.104.x <=v0.133.x + +type JestMockFn, TReturn> = { + (...args: TArguments): TReturn, + /** + * An object for introspecting mock calls + */ + mock: { + /** + * An array that represents all calls that have been made into this mock + * function. Each call is represented by an array of arguments that were + * passed during the call. + */ + calls: Array, + /** + * An array that contains all the object instances that have been + * instantiated from this mock function. + */ + instances: Array, + /** + * An array that contains all the object results that have been + * returned by this mock function call + */ + results: Array<{ + isThrow: boolean, + value: TReturn, + ... + }>, + ... + }, + /** + * Resets all information stored in the mockFn.mock.calls and + * mockFn.mock.instances arrays. Often this is useful when you want to clean + * up a mock's usage data between two assertions. + */ + mockClear(): void, + /** + * Resets all information stored in the mock. This is useful when you want to + * completely restore a mock back to its initial state. + */ + mockReset(): void, + /** + * Removes the mock and restores the initial implementation. This is useful + * when you want to mock functions in certain test cases and restore the + * original implementation in others. Beware that mockFn.mockRestore only + * works when mock was created with jest.spyOn. Thus you have to take care of + * restoration yourself when manually assigning jest.fn(). + */ + mockRestore(): void, + /** + * Accepts a function that should be used as the implementation of the mock. + * The mock itself will still record all calls that go into and instances + * that come from itself -- the only difference is that the implementation + * will also be executed when the mock is called. + */ + mockImplementation( + fn: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Accepts a function that will be used as an implementation of the mock for + * one call to the mocked function. Can be chained so that multiple function + * calls produce different results. + */ + mockImplementationOnce( + fn: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Accepts a string to use in test result output in place of "jest.fn()" to + * indicate which mock function is being referenced. + */ + mockName(name: string): JestMockFn, + /** + * Just a simple sugar function for returning `this` + */ + mockReturnThis(): void, + /** + * Accepts a value that will be returned whenever the mock function is called. + */ + mockReturnValue(value: TReturn): JestMockFn, + /** + * Sugar for only returning a value once inside your mock + */ + mockReturnValueOnce(value: TReturn): JestMockFn, + /** + * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value)) + */ + mockResolvedValue(value: TReturn): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) + */ + mockResolvedValueOnce( + value: TReturn + ): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementation(() => Promise.reject(value)) + */ + mockRejectedValue(value: TReturn): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value)) + */ + mockRejectedValueOnce(value: TReturn): JestMockFn>, + ... +}; + +type JestAsymmetricEqualityType = { + /** + * A custom Jasmine equality tester + */ + asymmetricMatch(value: mixed): boolean, + ... +}; + +type JestCallsType = { + allArgs(): mixed, + all(): mixed, + any(): boolean, + count(): number, + first(): mixed, + mostRecent(): mixed, + reset(): void, + ... +}; + +type JestClockType = { + install(): void, + mockDate(date: Date): void, + tick(milliseconds?: number): void, + uninstall(): void, + ... +}; + +type JestMatcherResult = { + message?: string | (() => string), + pass: boolean, + ... +}; + +type JestMatcher = ( + received: any, + ...actual: Array +) => JestMatcherResult | Promise; + +type JestPromiseType = { + /** + * Use rejects to unwrap the reason of a rejected promise so any other + * matcher can be chained. If the promise is fulfilled the assertion fails. + */ + rejects: JestExpectType, + /** + * Use resolves to unwrap the value of a fulfilled promise so any other + * matcher can be chained. If the promise is rejected the assertion fails. + */ + resolves: JestExpectType, + ... +}; + +/** + * Jest allows functions and classes to be used as test names in test() and + * describe() + */ +type JestTestName = string | Function; + +/** + * Plugin: jest-styled-components + */ + +type JestStyledComponentsMatcherValue = + | string + | JestAsymmetricEqualityType + | RegExp + | typeof undefined; + +type JestStyledComponentsMatcherOptions = { + media?: string, + modifier?: string, + supports?: string, + ... +}; + +type JestStyledComponentsMatchersType = { + toHaveStyleRule( + property: string, + value: JestStyledComponentsMatcherValue, + options?: JestStyledComponentsMatcherOptions + ): void, + ... +}; + +/** + * Plugin: jest-enzyme + */ +type EnzymeMatchersType = { + // 5.x + toBeEmpty(): void, + toBePresent(): void, + // 6.x + toBeChecked(): void, + toBeDisabled(): void, + toBeEmptyRender(): void, + toContainMatchingElement(selector: string): void, + toContainMatchingElements(n: number, selector: string): void, + toContainExactlyOneMatchingElement(selector: string): void, + toContainReact(element: React$Element): void, + toExist(): void, + toHaveClassName(className: string): void, + toHaveHTML(html: string): void, + toHaveProp: ((propKey: string, propValue?: any) => void) & + ((props: { ... }) => void), + toHaveRef(refName: string): void, + toHaveState: ((stateKey: string, stateValue?: any) => void) & + ((state: { ... }) => void), + toHaveStyle: ((styleKey: string, styleValue?: any) => void) & + ((style: { ... }) => void), + toHaveTagName(tagName: string): void, + toHaveText(text: string): void, + toHaveValue(value: any): void, + toIncludeText(text: string): void, + toMatchElement( + element: React$Element, + options?: {| ignoreProps?: boolean, verbose?: boolean |} + ): void, + toMatchSelector(selector: string): void, + // 7.x + toHaveDisplayName(name: string): void, + ... +}; + +// DOM testing library extensions (jest-dom) +// https://github.com/testing-library/jest-dom +type DomTestingLibraryType = { + /** + * @deprecated + */ + toBeInTheDOM(container?: HTMLElement): void, + + // 4.x + toBeInTheDocument(): void, + toBeVisible(): void, + toBeEmpty(): void, + toBeDisabled(): void, + toBeEnabled(): void, + toBeInvalid(): void, + toBeRequired(): void, + toBeValid(): void, + toContainElement(element: HTMLElement | null): void, + toContainHTML(htmlText: string): void, + toHaveAttribute(attr: string, value?: any): void, + toHaveClass(...classNames: string[]): void, + toHaveFocus(): void, + toHaveFormValues(expectedValues: { [name: string]: any, ... }): void, + toHaveStyle(css: string | { [name: string]: any, ... }): void, + toHaveTextContent( + text: string | RegExp, + options?: {| normalizeWhitespace: boolean |} + ): void, + toHaveValue(value?: string | string[] | number): void, + + // 5.x + toHaveDisplayValue(value: string | string[]): void, + toBeChecked(): void, + toBeEmptyDOMElement(): void, + toBePartiallyChecked(): void, + toHaveDescription(text: string | RegExp): void, + ... +}; + +// Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers +type JestJQueryMatchersType = { + toExist(): void, + toHaveLength(len: number): void, + toHaveId(id: string): void, + toHaveClass(className: string): void, + toHaveTag(tag: string): void, + toHaveAttr(key: string, val?: any): void, + toHaveProp(key: string, val?: any): void, + toHaveText(text: string | RegExp): void, + toHaveData(key: string, val?: any): void, + toHaveValue(val: any): void, + toHaveCss(css: { [key: string]: any, ... }): void, + toBeChecked(): void, + toBeDisabled(): void, + toBeEmpty(): void, + toBeHidden(): void, + toBeSelected(): void, + toBeVisible(): void, + toBeFocused(): void, + toBeInDom(): void, + toBeMatchedBy(sel: string): void, + toHaveDescendant(sel: string): void, + toHaveDescendantWithText(sel: string, text: string | RegExp): void, + ... +}; + +// Jest Extended Matchers: https://github.com/jest-community/jest-extended +type JestExtendedMatchersType = { + /** + * Note: Currently unimplemented + * Passing assertion + * + * @param {String} message + */ + // pass(message: string): void; + + /** + * Note: Currently unimplemented + * Failing assertion + * + * @param {String} message + */ + // fail(message: string): void; + + /** + * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. + */ + toBeEmpty(): void, + /** + * Use .toBeOneOf when checking if a value is a member of a given Array. + * @param {Array.<*>} members + */ + toBeOneOf(members: any[]): void, + /** + * Use `.toBeNil` when checking a value is `null` or `undefined`. + */ + toBeNil(): void, + /** + * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. + * @param {Function} predicate + */ + toSatisfy(predicate: (n: any) => boolean): void, + /** + * Use `.toBeArray` when checking if a value is an `Array`. + */ + toBeArray(): void, + /** + * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. + * @param {Number} x + */ + toBeArrayOfSize(x: number): void, + /** + * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. + * @param {Array.<*>} members + */ + toIncludeAllMembers(members: any[]): void, + /** + * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. + * @param {Array.<*>} members + */ + toIncludeAnyMembers(members: any[]): void, + /** + * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array. + * @param {Function} predicate + */ + toSatisfyAll(predicate: (n: any) => boolean): void, + /** + * Use `.toBeBoolean` when checking if a value is a `Boolean`. + */ + toBeBoolean(): void, + /** + * Use `.toBeTrue` when checking a value is equal (===) to `true`. + */ + toBeTrue(): void, + /** + * Use `.toBeFalse` when checking a value is equal (===) to `false`. + */ + toBeFalse(): void, + /** + * Use .toBeDate when checking if a value is a Date. + */ + toBeDate(): void, + /** + * Use `.toBeFunction` when checking if a value is a `Function`. + */ + toBeFunction(): void, + /** + * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. + * + * Note: Required Jest version >22 + * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same + * + * @param {Mock} mock + */ + toHaveBeenCalledBefore(mock: JestMockFn): void, + /** + * Use `.toBeNumber` when checking if a value is a `Number`. + */ + toBeNumber(): void, + /** + * Use `.toBeNaN` when checking a value is `NaN`. + */ + toBeNaN(): void, + /** + * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. + */ + toBeFinite(): void, + /** + * Use `.toBePositive` when checking if a value is a positive `Number`. + */ + toBePositive(): void, + /** + * Use `.toBeNegative` when checking if a value is a negative `Number`. + */ + toBeNegative(): void, + /** + * Use `.toBeEven` when checking if a value is an even `Number`. + */ + toBeEven(): void, + /** + * Use `.toBeOdd` when checking if a value is an odd `Number`. + */ + toBeOdd(): void, + /** + * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). + * + * @param {Number} start + * @param {Number} end + */ + toBeWithin(start: number, end: number): void, + /** + * Use `.toBeObject` when checking if a value is an `Object`. + */ + toBeObject(): void, + /** + * Use `.toContainKey` when checking if an object contains the provided key. + * + * @param {String} key + */ + toContainKey(key: string): void, + /** + * Use `.toContainKeys` when checking if an object has all of the provided keys. + * + * @param {Array.} keys + */ + toContainKeys(keys: string[]): void, + /** + * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. + * + * @param {Array.} keys + */ + toContainAllKeys(keys: string[]): void, + /** + * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. + * + * @param {Array.} keys + */ + toContainAnyKeys(keys: string[]): void, + /** + * Use `.toContainValue` when checking if an object contains the provided value. + * + * @param {*} value + */ + toContainValue(value: any): void, + /** + * Use `.toContainValues` when checking if an object contains all of the provided values. + * + * @param {Array.<*>} values + */ + toContainValues(values: any[]): void, + /** + * Use `.toContainAllValues` when checking if an object only contains all of the provided values. + * + * @param {Array.<*>} values + */ + toContainAllValues(values: any[]): void, + /** + * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. + * + * @param {Array.<*>} values + */ + toContainAnyValues(values: any[]): void, + /** + * Use `.toContainEntry` when checking if an object contains the provided entry. + * + * @param {Array.} entry + */ + toContainEntry(entry: [string, string]): void, + /** + * Use `.toContainEntries` when checking if an object contains all of the provided entries. + * + * @param {Array.>} entries + */ + toContainEntries(entries: [string, string][]): void, + /** + * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. + * + * @param {Array.>} entries + */ + toContainAllEntries(entries: [string, string][]): void, + /** + * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. + * + * @param {Array.>} entries + */ + toContainAnyEntries(entries: [string, string][]): void, + /** + * Use `.toBeExtensible` when checking if an object is extensible. + */ + toBeExtensible(): void, + /** + * Use `.toBeFrozen` when checking if an object is frozen. + */ + toBeFrozen(): void, + /** + * Use `.toBeSealed` when checking if an object is sealed. + */ + toBeSealed(): void, + /** + * Use `.toBeString` when checking if a value is a `String`. + */ + toBeString(): void, + /** + * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. + * + * @param {String} string + */ + toEqualCaseInsensitive(string: string): void, + /** + * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. + * + * @param {String} prefix + */ + toStartWith(prefix: string): void, + /** + * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. + * + * @param {String} suffix + */ + toEndWith(suffix: string): void, + /** + * Use `.toInclude` when checking if a `String` includes the given `String` substring. + * + * @param {String} substring + */ + toInclude(substring: string): void, + /** + * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. + * + * @param {String} substring + * @param {Number} times + */ + toIncludeRepeated(substring: string, times: number): void, + /** + * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. + * + * @param {Array.} substring + */ + toIncludeMultiple(substring: string[]): void, + ... +}; + +// Diffing snapshot utility for Jest (snapshot-diff) +// https://github.com/jest-community/snapshot-diff +type SnapshotDiffType = { + /** + * Compare the difference between the actual in the `expect()` + * vs the object inside `valueB` with some extra options. + */ + toMatchDiffSnapshot( + valueB: any, + options?: {| + expand?: boolean, + colors?: boolean, + contextLines?: number, + stablePatchmarks?: boolean, + aAnnotation?: string, + bAnnotation?: string, + |}, + testName?: string + ): void, + ... +}; + +interface JestExpectType { + not: JestExpectType & + EnzymeMatchersType & + DomTestingLibraryType & + JestJQueryMatchersType & + JestStyledComponentsMatchersType & + JestExtendedMatchersType & + SnapshotDiffType; + /** + * If you have a mock function, you can use .lastCalledWith to test what + * arguments it was last called with. + */ + lastCalledWith(...args: Array): void; + /** + * toBe just checks that a value is what you expect. It uses === to check + * strict equality. + */ + toBe(value: any): void; + /** + * Use .toBeCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toBeCalledWith(...args: Array): void; + /** + * Using exact equality with floating point numbers is a bad idea. Rounding + * means that intuitive things fail. + */ + toBeCloseTo(num: number, delta: any): void; + /** + * Use .toBeDefined to check that a variable is not undefined. + */ + toBeDefined(): void; + /** + * Use .toBeFalsy when you don't care what a value is, you just want to + * ensure a value is false in a boolean context. + */ + toBeFalsy(): void; + /** + * To compare floating point numbers, you can use toBeGreaterThan. + */ + toBeGreaterThan(number: number): void; + /** + * To compare floating point numbers, you can use toBeGreaterThanOrEqual. + */ + toBeGreaterThanOrEqual(number: number): void; + /** + * To compare floating point numbers, you can use toBeLessThan. + */ + toBeLessThan(number: number): void; + /** + * To compare floating point numbers, you can use toBeLessThanOrEqual. + */ + toBeLessThanOrEqual(number: number): void; + /** + * Use .toBeInstanceOf(Class) to check that an object is an instance of a + * class. + */ + toBeInstanceOf(cls: Class<*>): void; + /** + * .toBeNull() is the same as .toBe(null) but the error messages are a bit + * nicer. + */ + toBeNull(): void; + /** + * Use .toBeTruthy when you don't care what a value is, you just want to + * ensure a value is true in a boolean context. + */ + toBeTruthy(): void; + /** + * Use .toBeUndefined to check that a variable is undefined. + */ + toBeUndefined(): void; + /** + * Use .toContain when you want to check that an item is in a list. For + * testing the items in the list, this uses ===, a strict equality check. + */ + toContain(item: any): void; + /** + * Use .toContainEqual when you want to check that an item is in a list. For + * testing the items in the list, this matcher recursively checks the + * equality of all fields, rather than checking for object identity. + */ + toContainEqual(item: any): void; + /** + * Use .toEqual when you want to check that two objects have the same value. + * This matcher recursively checks the equality of all fields, rather than + * checking for object identity. + */ + toEqual(value: any): void; + /** + * Use .toHaveBeenCalled to ensure that a mock function got called. + */ + toHaveBeenCalled(): void; + toBeCalled(): void; + /** + * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact + * number of times. + */ + toHaveBeenCalledTimes(number: number): void; + toBeCalledTimes(number: number): void; + /** + * + */ + toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; + nthCalledWith(nthCall: number, ...args: Array): void; + /** + * + */ + toHaveReturned(): void; + toReturn(): void; + /** + * + */ + toHaveReturnedTimes(number: number): void; + toReturnTimes(number: number): void; + /** + * + */ + toHaveReturnedWith(value: any): void; + toReturnWith(value: any): void; + /** + * + */ + toHaveLastReturnedWith(value: any): void; + lastReturnedWith(value: any): void; + /** + * + */ + toHaveNthReturnedWith(nthCall: number, value: any): void; + nthReturnedWith(nthCall: number, value: any): void; + /** + * Use .toHaveBeenCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toHaveBeenCalledWith(...args: Array): void; + toBeCalledWith(...args: Array): void; + /** + * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called + * with specific arguments. + */ + toHaveBeenLastCalledWith(...args: Array): void; + lastCalledWith(...args: Array): void; + /** + * Check that an object has a .length property and it is set to a certain + * numeric value. + */ + toHaveLength(number: number): void; + /** + * + */ + toHaveProperty(propPath: string | $ReadOnlyArray, value?: any): void; + /** + * Use .toMatch to check that a string matches a regular expression or string. + */ + toMatch(regexpOrString: RegExp | string): void; + /** + * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. + */ + toMatchObject(object: Object | Array): void; + /** + * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. + */ + toStrictEqual(value: any): void; + /** + * This ensures that an Object matches the most recent snapshot. + */ + toMatchSnapshot(propertyMatchers?: any, name?: string): void; + /** + * This ensures that an Object matches the most recent snapshot. + */ + toMatchSnapshot(name: string): void; + + toMatchInlineSnapshot(snapshot?: string): void; + toMatchInlineSnapshot(propertyMatchers?: any, snapshot?: string): void; + /** + * Use .toThrow to test that a function throws when it is called. + * If you want to test that a specific error gets thrown, you can provide an + * argument to toThrow. The argument can be a string for the error message, + * a class for the error, or a regex that should match the error. + * + * Alias: .toThrowError + */ + toThrow(message?: string | Error | Class | RegExp): void; + toThrowError(message?: string | Error | Class | RegExp): void; + /** + * Use .toThrowErrorMatchingSnapshot to test that a function throws a error + * matching the most recent snapshot when it is called. + */ + toThrowErrorMatchingSnapshot(): void; + toThrowErrorMatchingInlineSnapshot(snapshot?: string): void; +} + +type JestObjectType = { + /** + * Disables automatic mocking in the module loader. + * + * After this method is called, all `require()`s will return the real + * versions of each module (rather than a mocked version). + */ + disableAutomock(): JestObjectType, + /** + * An un-hoisted version of disableAutomock + */ + autoMockOff(): JestObjectType, + /** + * Enables automatic mocking in the module loader. + */ + enableAutomock(): JestObjectType, + /** + * An un-hoisted version of enableAutomock + */ + autoMockOn(): JestObjectType, + /** + * Clears the mock.calls and mock.instances properties of all mocks. + * Equivalent to calling .mockClear() on every mocked function. + */ + clearAllMocks(): JestObjectType, + /** + * Resets the state of all mocks. Equivalent to calling .mockReset() on every + * mocked function. + */ + resetAllMocks(): JestObjectType, + /** + * Restores all mocks back to their original value. + */ + restoreAllMocks(): JestObjectType, + /** + * Removes any pending timers from the timer system. + */ + clearAllTimers(): void, + /** + * Returns the number of fake timers still left to run. + */ + getTimerCount(): number, + /** + * Set the current system time used by fake timers. + * Simulates a user changing the system clock while your program is running. + * It affects the current time but it does not in itself cause + * e.g. timers to fire; they will fire exactly as they would have done + * without the call to jest.setSystemTime(). + */ + setSystemTime(now?: number | Date): void, + /** + * The same as `mock` but not moved to the top of the expectation by + * babel-jest. + */ + doMock(moduleName: string, moduleFactory?: any): JestObjectType, + /** + * The same as `unmock` but not moved to the top of the expectation by + * babel-jest. + */ + dontMock(moduleName: string): JestObjectType, + /** + * Returns a new, unused mock function. Optionally takes a mock + * implementation. + */ + fn, TReturn>( + implementation?: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Determines if the given function is a mocked function. + */ + isMockFunction(fn: Function): boolean, + /** + * Given the name of a module, use the automatic mocking system to generate a + * mocked version of the module for you. + */ + genMockFromModule(moduleName: string): any, + /** + * Mocks a module with an auto-mocked version when it is being required. + * + * The second argument can be used to specify an explicit module factory that + * is being run instead of using Jest's automocking feature. + * + * The third argument can be used to create virtual mocks -- mocks of modules + * that don't exist anywhere in the system. + */ + mock( + moduleName: string, + moduleFactory?: any, + options?: Object + ): JestObjectType, + /** + * Returns the actual module instead of a mock, bypassing all checks on + * whether the module should receive a mock implementation or not. + */ + requireActual(m: $Flow$ModuleRef | string): T, + /** + * Returns a mock module instead of the actual module, bypassing all checks + * on whether the module should be required normally or not. + */ + requireMock(moduleName: string): any, + /** + * Resets the module registry - the cache of all required modules. This is + * useful to isolate modules where local state might conflict between tests. + */ + resetModules(): JestObjectType, + /** + * Creates a sandbox registry for the modules that are loaded inside the + * callback function. This is useful to isolate specific modules for every + * test so that local module state doesn't conflict between tests. + */ + isolateModules(fn: () => void): JestObjectType, + /** + * Exhausts the micro-task queue (usually interfaced in node via + * process.nextTick). + */ + runAllTicks(): void, + /** + * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), + * setInterval(), and setImmediate()). + */ + runAllTimers(): void, + /** + * Exhausts all tasks queued by setImmediate(). + */ + runAllImmediates(): void, + /** + * Executes only the macro task queue (i.e. all tasks queued by setTimeout() + * or setInterval() and setImmediate()). + */ + advanceTimersByTime(msToRun: number): void, + /** + * Executes only the macro-tasks that are currently pending (i.e., only the + * tasks that have been queued by setTimeout() or setInterval() up to this + * point) + */ + runOnlyPendingTimers(): void, + /** + * Explicitly supplies the mock object that the module system should return + * for the specified module. Note: It is recommended to use jest.mock() + * instead. + */ + setMock(moduleName: string, moduleExports: any): JestObjectType, + /** + * Indicates that the module system should never return a mocked version of + * the specified module from require() (e.g. that it should always return the + * real module). + */ + unmock(moduleName: string): JestObjectType, + /** + * Instructs Jest to use fake versions of the standard timer functions + * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, + * setImmediate and clearImmediate). + */ + useFakeTimers(mode?: 'modern' | 'legacy'): JestObjectType, + /** + * Instructs Jest to use the real versions of the standard timer functions. + */ + useRealTimers(): JestObjectType, + /** + * Creates a mock function similar to jest.fn but also tracks calls to + * object[methodName]. + */ + spyOn( + object: Object, + methodName: string, + accessType?: 'get' | 'set' + ): JestMockFn, + /** + * Set the default timeout interval for tests and before/after hooks in milliseconds. + * Note: The default timeout interval is 5 seconds if this method is not called. + */ + setTimeout(timeout: number): JestObjectType, + ... +}; + +type JestSpyType = { calls: JestCallsType, ... }; + +type JestDoneFn = {| + (error?: Error): void, + fail: (error: Error) => void, +|}; + +/** Runs this function after every test inside this context */ +declare function afterEach( + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; +/** Runs this function before every test inside this context */ +declare function beforeEach( + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; +/** Runs this function after all tests have finished inside this context */ +declare function afterAll( + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; +/** Runs this function before any tests have started inside this context */ +declare function beforeAll( + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; + +/** A context for grouping tests together */ +declare var describe: { + /** + * Creates a block that groups together several related tests in one "test suite" + */ + (name: JestTestName, fn: () => void): void, + /** + * Only run this describe block + */ + only(name: JestTestName, fn: () => void): void, + /** + * Skip running this describe block + */ + skip(name: JestTestName, fn: () => void): void, + /** + * each runs this test against array of argument arrays per each run + * + * @param {table} table of Test + */ + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void, + ... +}; + +/** An individual test unit */ +declare var it: { + /** + * An individual test unit + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + ( + name: JestTestName, + fn?: (done: JestDoneFn) => ?Promise, + timeout?: number + ): void, + /** + * Only run this test + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + only: {| + ( + name: JestTestName, + fn?: (done: JestDoneFn) => ?Promise, + timeout?: number + ): void, + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void, + |}, + /** + * Skip running this test + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + skip( + name: JestTestName, + fn?: (done: JestDoneFn) => ?Promise, + timeout?: number + ): void, + /** + * Highlight planned tests in the summary output + * + * @param {String} Name of Test to do + */ + todo(name: string): void, + /** + * Run the test concurrently + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + concurrent( + name: JestTestName, + fn?: (done: JestDoneFn) => ?Promise, + timeout?: number + ): void, + /** + * each runs this test against array of argument arrays per each run + * + * @param {table} table of Test + */ + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void, + ... +}; + +declare function fit( + name: JestTestName, + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; +/** An individual test unit */ +declare var test: typeof it; +/** A disabled group of tests */ +declare var xdescribe: typeof describe; +/** A focused group of tests */ +declare var fdescribe: typeof describe; +/** A disabled individual test */ +declare var xit: typeof it; +/** A disabled individual test */ +declare var xtest: typeof it; + +type JestPrettyFormatColors = { + comment: { + close: string, + open: string, + ... + }, + content: { + close: string, + open: string, + ... + }, + prop: { + close: string, + open: string, + ... + }, + tag: { + close: string, + open: string, + ... + }, + value: { + close: string, + open: string, + ... + }, + ... +}; + +type JestPrettyFormatIndent = (string) => string; +type JestPrettyFormatRefs = Array; +type JestPrettyFormatPrint = (any) => string; +type JestPrettyFormatStringOrNull = string | null; + +type JestPrettyFormatOptions = {| + callToJSON: boolean, + edgeSpacing: string, + escapeRegex: boolean, + highlight: boolean, + indent: number, + maxDepth: number, + min: boolean, + plugins: JestPrettyFormatPlugins, + printFunctionName: boolean, + spacing: string, + theme: {| + comment: string, + content: string, + prop: string, + tag: string, + value: string, + |}, +|}; + +type JestPrettyFormatPlugin = { + print: ( + val: any, + serialize: JestPrettyFormatPrint, + indent: JestPrettyFormatIndent, + opts: JestPrettyFormatOptions, + colors: JestPrettyFormatColors + ) => string, + test: (any) => boolean, + ... +}; + +type JestPrettyFormatPlugins = Array; + +/** The expect function is used every time you want to test a value */ +declare var expect: { + /** The object that you want to make assertions against */ + ( + value: any + ): JestExpectType & + JestPromiseType & + EnzymeMatchersType & + DomTestingLibraryType & + JestJQueryMatchersType & + JestStyledComponentsMatchersType & + JestExtendedMatchersType & + SnapshotDiffType, + /** Add additional Jasmine matchers to Jest's roster */ + extend(matchers: { [name: string]: JestMatcher, ... }): void, + /** Add a module that formats application-specific data structures. */ + addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, + assertions(expectedAssertions: number): void, + hasAssertions(): void, + any(value: mixed): JestAsymmetricEqualityType, + anything(): any, + arrayContaining(value: Array): Array, + objectContaining(value: Object): Object, + /** Matches any received string that contains the exact expected string. */ + stringContaining(value: string): string, + stringMatching(value: string | RegExp): string, + not: { + arrayContaining: (value: $ReadOnlyArray) => Array, + objectContaining: (value: { ... }) => Object, + stringContaining: (value: string) => string, + stringMatching: (value: string | RegExp) => string, + ... + }, + ... +}; + +// TODO handle return type +// http://jasmine.github.io/2.4/introduction.html#section-Spies +declare function spyOn(value: mixed, method: string): Object; + +/** Holds all functions related to manipulating test runner */ +declare var jest: JestObjectType; + +/** + * The global Jasmine object, this is generally not exposed as the public API, + * using features inside here could break in later versions of Jest. + */ +declare var jasmine: { + DEFAULT_TIMEOUT_INTERVAL: number, + any(value: mixed): JestAsymmetricEqualityType, + anything(): any, + arrayContaining(value: Array): Array, + clock(): JestClockType, + createSpy(name: string): JestSpyType, + createSpyObj( + baseName: string, + methodNames: Array + ): { [methodName: string]: JestSpyType, ... }, + objectContaining(value: Object): Object, + stringMatching(value: string): string, + ... +}; diff --git a/installers/common/MacInstaller.hs b/installers/common/MacInstaller.hs index 60b4db0c4e..666907a6d0 100644 --- a/installers/common/MacInstaller.hs +++ b/installers/common/MacInstaller.hs @@ -152,6 +152,8 @@ sign_cmd "$ABS_PATH/Contents/Resources/app/build/HID.node" sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/keccak/bin/darwin-x64-"*"/keccak.node" sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/keccak/build/Release/addon.node" sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/keccak/prebuilds/darwin-x64/node.napi.node" +sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/blake-hash/prebuilds/darwin-x64/node.napi.node" +sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/tiny-secp256k1/build/Release/secp256k1.node" # Sign the whole component deeply sign_cmd "$ABS_PATH" @@ -232,20 +234,24 @@ buildElectronApp darwinConfig@DarwinConfig{dcAppName, dcAppNameApp} installerCon externalYarn :: [FilePath] externalYarn = [ "@babel" + , "@protobufjs" , "@trezor" , "base-x" , "base64-js" , "bchaddrjs" + , "bech32" , "big-integer" - , "bigi" , "bignumber.js" , "bip66" , "bitcoin-ops" , "blake2b" + , "blake-hash" , "blake2b-wasm" + , "bn.js" + , "brorand" , "bs58" , "bs58check" - , "bytebuffer-old-fixed-webpack" + , "bytebuffer" , "call-bind" , "cashaddrjs" , "cbor-web" @@ -253,13 +259,14 @@ buildElectronApp darwinConfig@DarwinConfig{dcAppName, dcAppNameApp} installerCon , "create-hmac" , "cross-fetch" , "define-properties" - , "ecurve" + , "elliptic" , "es-abstract" , "function-bind" , "get-intrinsic" , "has" , "has-symbols" - , "hd-wallet" + , "hash.js" + , "hmac-drbg" , "ieee754" , "inherits" , "int64-buffer" @@ -267,16 +274,16 @@ buildElectronApp darwinConfig@DarwinConfig{dcAppName, dcAppNameApp} installerCon , "json-stable-stringify" , "keccak" , "long" - , "merkle-lib" , "ms" + , "minimalistic-assert" + , "minimalistic-crypto-utils" , "nanoassert" , "node-fetch" , "object-keys" , "object.values" , "parse-uri" - , "protobufjs-old-fixed-webpack" + , "protobufjs" , "pushdata-bitcoin" - , "queue" , "randombytes" , "regenerator-runtime" , "runtypes" @@ -284,8 +291,9 @@ buildElectronApp darwinConfig@DarwinConfig{dcAppName, dcAppNameApp} installerCon , "semver-compare" , "tiny-worker" , "trezor-connect" - , "trezor-link" + , "tiny-secp256k1" , "typeforce" + , "util-deprecate" , "varuint-bitcoin" , "wif" ] diff --git a/installers/daedalus-installer.nix b/installers/daedalus-installer.nix index 41771d00a5..e4136cb22d 100644 --- a/installers/daedalus-installer.nix +++ b/installers/daedalus-installer.nix @@ -1,7 +1,7 @@ { mkDerivation, aeson, base, bytestring, containers, directory , filepath, foldl, github, Glob, hspec, lens-aeson, managed , megaparsec, microlens, network-uri, nsis, optional-args -, optparse-applicative, optparse-generic, split, stdenv +, optparse-applicative, optparse-generic, split, lib , system-filepath, temporary, text, turtle, universum, raw-strings-qq , unordered-containers, wreq, yaml, zip-archive }: @@ -30,5 +30,6 @@ mkDerivation { system-filepath temporary text turtle universum yaml ]; description = "Daedalus Installer Builder"; - license = stdenv.lib.licenses.asl20; + + license = lib.licenses.asl20; } diff --git a/installers/dhall-haskell.nix b/installers/dhall-haskell.nix index c2070cb015..23c6946579 100644 --- a/installers/dhall-haskell.nix +++ b/installers/dhall-haskell.nix @@ -4,7 +4,7 @@ , fetchgit, filepath, formatting, haskeline, http-client , http-client-tls, insert-ordered-containers, lens-family-core , memory, mtl, optparse-generic, parsers, prettyprinter -, prettyprinter-ansi-terminal, repline, scientific, stdenv, tasty +, prettyprinter-ansi-terminal, repline, scientific, stdenv, tasty, lib , tasty-hunit, text, text-format, transformers, trifecta, unordered-containers , vector }: @@ -35,5 +35,5 @@ mkDerivation { tasty-hunit text vector text-format ]; description = "A configuration language guaranteed to terminate"; - license = stdenv.lib.licenses.bsd3; + license = lib.licenses.bsd3; } diff --git a/installers/dhall-json.nix b/installers/dhall-json.nix index 55f5a75164..ef72c3dc7b 100644 --- a/installers/dhall-json.nix +++ b/installers/dhall-json.nix @@ -1,5 +1,5 @@ { mkDerivation, aeson, aeson-pretty, base, bytestring, dhall -, fetchgit, optparse-generic, stdenv, text, trifecta +, fetchgit, optparse-generic, stdenv, text, trifecta, lib , unordered-containers, yaml }: mkDerivation { @@ -19,5 +19,5 @@ mkDerivation { aeson aeson-pretty base bytestring dhall optparse-generic text yaml ]; description = "Compile Dhall to JSON or YAML"; - license = stdenv.lib.licenses.bsd3; + license = lib.licenses.bsd3; } diff --git a/installers/nix/electron.nix b/installers/nix/electron.nix index e36d0a6df8..8c02265d18 100644 --- a/installers/nix/electron.nix +++ b/installers/nix/electron.nix @@ -1,13 +1,13 @@ -{ stdenv, libXScrnSaver, makeWrapper, fetchurl, unzip, atomEnv, libuuid, at-spi2-atk, at_spi2_core, libxshmfence, - libdrm, libxkbcommon, mesa }: +{ stdenv, lib, makeWrapper, fetchurl, unzip, atomEnv, libuuid, at-spi2-atk, at_spi2_core, libxshmfence, + libxkbcommon }: let - version = "13.1.1"; + version = "13.6.3"; name = "electron-${version}"; throwSystem = throw "Unsupported system: ${stdenv.hostPlatform.system}"; - meta = with stdenv.lib; { + meta = with lib; { description = "Cross platform desktop application shell"; homepage = https://github.com/electron/electron; license = licenses.mit; @@ -21,19 +21,19 @@ let src = { i686-linux = fetchurl { url = "https://github.com/electron/electron/releases/download/v${version}/electron-v${version}-linux-ia32.zip"; - sha256 = "fed00edaaba0c4a615fe835baf7d0d0ff893dff902800006bf63cc994c24d3dd"; + sha256 = "db9261c05ed57af2fcd4a84b89d299c76948b9d57ce0dba38e3240eb43935257"; }; x86_64-linux = fetchurl { url = "https://github.com/electron/electron/releases/download/v${version}/electron-v${version}-linux-x64.zip"; - sha256 = "eb6ae81d71a4d390ec5140d907b191a84c37621176eec9369bb6fc3bf8570e3b"; + sha256 = "7607422a4ba80cda4bd7fefb2fbe2f4e0b9a73db92e1e82dc01012a85b5d0d2b"; }; armv7l-linux = fetchurl { url = "https://github.com/electron/electron/releases/download/v${version}/electron-v${version}-linux-armv7l.zip"; - sha256 = "7e745a38c6761fa9826b3b9b8d0bd060126a3949da6f3f09f11b842e5e22cee4"; + sha256 = "a293a9684e16a427a9f68d101814575a4b1dd232dc3fca47552f906019a6cadc"; }; aarch64-linux = fetchurl { url = "https://github.com/electron/electron/releases/download/v${version}/electron-v${version}-linux-arm64.zip"; - sha256 = "445c88e9c9b33abbdb263103736fb5203938b0643bc5377fbdf12b444d26f211"; + sha256 = "1599d259832c806b98751a68fb93112711963d259024f0e36f12f064995b3251"; }; }.${stdenv.hostPlatform.system} or throwSystem; @@ -48,15 +48,8 @@ let patchelf \ --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \ - --set-rpath "${atomEnv.libPath}:${stdenv.lib.makeLibraryPath [ libuuid at-spi2-atk at_spi2_core ]}:$out/lib/electron" \ + --set-rpath "${atomEnv.libPath}:${lib.makeLibraryPath [ libuuid at-spi2-atk at_spi2_core libxshmfence libxkbcommon ]}:$out/lib/electron" \ $out/lib/electron/electron - - wrapProgram $out/lib/electron/electron \ - --prefix LD_PRELOAD : ${stdenv.lib.makeLibraryPath [ libXScrnSaver ]}/libXss.so.1 \ - --prefix LD_PRELOAD : ${stdenv.lib.makeLibraryPath [ libdrm ]}/libdrm.so.2 \ - --prefix LD_PRELOAD : ${stdenv.lib.makeLibraryPath [ libxkbcommon ]}/libxkbcommon.so.0 \ - --prefix LD_PRELOAD : ${stdenv.lib.makeLibraryPath [ mesa ]}/libgbm.so.1 \ - --prefix LD_PRELOAD : ${stdenv.lib.makeLibraryPath [ libxshmfence ]}/libxshmfence.so.1 ''; }; @@ -66,11 +59,11 @@ let src = { x86_64-darwin = fetchurl { url = "https://github.com/electron/electron/releases/download/v${version}/electron-v${version}-darwin-x64.zip"; - sha256 = "1594ba9aa2e2aa059a03e6b70e16b8116de1998b38f8360801e113fa8d72938c"; + sha256 = "6bf09794d6f020bbaaf806a7758da125137b3c96646f4503eb81b9541e50e02f"; }; aarch64-darwin = fetchurl { url = "https://github.com/electron/electron/releases/download/v${version}/electron-v${version}-darwin-arm64.zip"; - sha256 = "7045538917c36214127b7f11a3223396c7199ac19e989e5648a0963773962e6c"; + sha256 = "374ddf0581794b31eee900828172f9218193c032c0e46bffcfac6aec95c22f1a"; }; }.${stdenv.hostPlatform.system} or throwSystem; diff --git a/installers/overlays/dhall-json.nix b/installers/overlays/dhall-json.nix index e3717748f5..357cb9143e 100644 --- a/installers/overlays/dhall-json.nix +++ b/installers/overlays/dhall-json.nix @@ -1,7 +1,7 @@ { mkDerivation, aeson, aeson-pretty, ansi-terminal, base , bytestring, containers, dhall, exceptions, filepath, libyaml , optparse-applicative, prettyprinter, prettyprinter-ansi-terminal -, scientific, stdenv, tasty, tasty-hunit, text +, scientific, stdenv, tasty, tasty-hunit, text, lib , unordered-containers, vector, yaml }: mkDerivation { @@ -25,5 +25,5 @@ mkDerivation { aeson base bytestring dhall tasty tasty-hunit text ]; description = "Convert between Dhall and JSON or YAML"; - license = stdenv.lib.licenses.bsd3; + license = lib.licenses.bsd3; } diff --git a/installers/overlays/dhall.nix b/installers/overlays/dhall.nix index a95d44588a..ecd380440a 100644 --- a/installers/overlays/dhall.nix +++ b/installers/overlays/dhall.nix @@ -5,7 +5,7 @@ , generic-random, haskeline, http-client, http-client-tls , http-types, lens-family-core, megaparsec, memory, mockery, mtl , network-uri, optparse-applicative, parsers, prettyprinter -, prettyprinter-ansi-terminal, profunctors, QuickCheck +, prettyprinter-ansi-terminal, profunctors, QuickCheck, lib , quickcheck-instances, repline, scientific, semigroups, serialise , spoon, stdenv, tasty, tasty-expected-failure, tasty-hunit , tasty-quickcheck, template-haskell, text, th-lift-instances @@ -40,5 +40,5 @@ mkDerivation { base bytestring containers directory gauge serialise text ]; description = "A configuration language guaranteed to terminate"; - license = stdenv.lib.licenses.bsd3; + license = lib.licenses.bsd3; } diff --git a/installers/overlays/nsis.nix b/installers/overlays/nsis.nix index 9548ee58db..d7be8cad5d 100644 --- a/installers/overlays/nsis.nix +++ b/installers/overlays/nsis.nix @@ -1,4 +1,4 @@ -{ mkDerivation, base, directory, fetchFromGitHub, process, stdenv +{ mkDerivation, base, directory, fetchFromGitHub, process, stdenv, lib , transformers, uniplate }: mkDerivation { @@ -16,5 +16,5 @@ mkDerivation { ]; homepage = "https://github.com/ndmitchell/nsis#readme"; description = "DSL for producing Windows Installer using NSIS"; - license = stdenv.lib.licenses.bsd3; + license = lib.licenses.bsd3; } diff --git a/installers/overlays/required.nix b/installers/overlays/required.nix index d8f096a36f..43168cae20 100644 --- a/installers/overlays/required.nix +++ b/installers/overlays/required.nix @@ -8,5 +8,5 @@ self: super: { dhall = dontCheck (doJailbreak (self.callPackage ./dhall.nix {})); universum = dontCheck (self.callPackage ./universum.nix {}); nsis = self.callPackage ./nsis.nix {}; - github = self.callHackage "github" "0.26" {}; + github = self.callHackage "github" "0.27" {}; } diff --git a/installers/overlays/universum.nix b/installers/overlays/universum.nix index 39b70c7cb2..627ffcd69b 100644 --- a/installers/overlays/universum.nix +++ b/installers/overlays/universum.nix @@ -1,6 +1,6 @@ { mkDerivation, base, bytestring, containers, deepseq, doctest , gauge, ghc-prim, Glob, hashable, hedgehog, microlens -, microlens-mtl, mtl, safe-exceptions, stdenv, stm, tasty +, microlens-mtl, mtl, safe-exceptions, stdenv, lib, stm, tasty , tasty-hedgehog, text, transformers, unordered-containers , utf8-string, vector }: @@ -22,5 +22,5 @@ mkDerivation { ]; homepage = "https://github.com/serokell/universum"; description = "Custom prelude used in Serokell"; - license = stdenv.lib.licenses.mit; + license = lib.licenses.mit; } diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000000..fba7a4c536 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,201 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +module.exports = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/private/tmp/jest_dx", + + // Automatically clear mock calls and instances between every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: 'coverage', + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + // coverageProvider: "babel", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + globals: { + environment: { + network: {}, + }, + }, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + moduleNameMapper: { + // Jest does not support WASM imports from ESM modules + // https://github.com/facebook/jest/issues/9430 + '^@iohk-jormungandr/wallet-js$': 'identity-obj-proxy', + }, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state between every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state between every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + roots: ['/tests', '/source'], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: 'jest-environment-jsdom', + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href + // testURL: "http://localhost", + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + transform: { + '\\.[jt]sx?$': 'babel-jest', + '^.+\\.svg$': 'jest-svg-transformer', + '.+\\.(css|styl|less|sass|scss)$': 'jest-css-modules-transform', + }, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/nix/darwin-launcher.nix b/nix/darwin-launcher.nix index b72f454ec2..3b291876a4 100644 --- a/nix/darwin-launcher.nix +++ b/nix/darwin-launcher.nix @@ -12,6 +12,7 @@ let passthru = { inherit test; }; } '' export HOME=$NIX_BUILD_TOP + go env -w GO111MODULE=off mkdir -p $out/bin cp ${./darwin-launcher.go} darwin-launcher.go CGO_ENABLED=0 go build -a -o $out/bin/darwin-launcher diff --git a/nix/nsis-inner.nix b/nix/nsis-inner.nix index c1729b6625..32ef9d2377 100644 --- a/nix/nsis-inner.nix +++ b/nix/nsis-inner.nix @@ -42,7 +42,7 @@ in stdenv.mkDerivation { scons install "${sconsArgs}" ''; - meta = with stdenv.lib; { + meta = with lib; { descripition = "System to create Windows installers"; homepage = "https://nsis.sourceforge.io/"; }; diff --git a/nix/sources.json b/nix/sources.json index 32db879d9c..940f079cd6 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -1,14 +1,14 @@ { "cardano-node": { - "branch": "refs/tags/1.29.0-rc2", + "branch": "tags/1.33.0", "description": null, "homepage": null, "owner": "input-output-hk", "repo": "cardano-node", - "rev": "cc78734d263d0eec2b12070380cdfea02a5a8342", - "sha256": "1bqrbgc11c4x5mrcm7shyvfs05fqi4v1pr4y9pw2aprab277czjr", + "rev": "814df2c146f5d56f8c35a681fe75e85b905aed5d", + "sha256": "1hr00wqzmcyc3x0kp2hyw78rfmimf6z4zd4vv85b9zv3nqbjgrik", "type": "tarball", - "url": "https://github.com/input-output-hk/cardano-node/archive/cc78734d263d0eec2b12070380cdfea02a5a8342.tar.gz", + "url": "https://github.com/input-output-hk/cardano-node/archive/814df2c146f5d56f8c35a681fe75e85b905aed5d.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "cardano-shell": { @@ -24,15 +24,15 @@ "url_template": "https://github.com///archive/.tar.gz" }, "cardano-wallet": { - "branch": "anviking/ADP-1081/node-alonzo-rc", + "branch": "master", "description": "Official Wallet Backend & API for Cardano decentralized", "homepage": null, "owner": "input-output-hk", "repo": "cardano-wallet", - "rev": "6626489542ce442a8451fb46fb5339346e62b4ee", - "sha256": "0n1vgbz3m73v33462ifg3qas9djwlpcvi1m0qxc7j15l3w5qhlw5", + "rev": "a5085acbd2670c24251cf8d76a4e83c77a2679ba", + "sha256": "1apzfy7qdgf6l0lb3icqz3rvaq2w3a53xq6wvhqnbfi8i7cacy03", "type": "tarball", - "url": "https://github.com/input-output-hk/cardano-wallet/archive/6626489542ce442a8451fb46fb5339346e62b4ee.tar.gz", + "url": "https://github.com/input-output-hk/cardano-wallet/archive/a5085acbd2670c24251cf8d76a4e83c77a2679ba.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "gitignore": { @@ -101,10 +101,22 @@ "homepage": "", "owner": "input-output-hk", "repo": "nix-bundle", - "rev": "acf3b142c1fa332d4f9050e7cdfb3c3f8091e78b", - "sha256": "0pcc8bh1x3shr6k2h8zw05n0apm5ppx6nk07xp89ljldah4mmc6c", + "rev": "a43e9280628d6e7fcc2f89257106f5262d531bc7", + "sha256": "10qgincrs8fjdl16mld6lzd69syhyzwx65lcbz4widnkdvhlwh3i", + "type": "tarball", + "url": "https://github.com/input-output-hk/nix-bundle/archive/a43e9280628d6e7fcc2f89257106f5262d531bc7.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs": { + "branch": "nixos-unstable", + "description": "Nix Packages collection", + "homepage": "", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b67e752c29f18a0ca5534a07661366d6a2c2e649", + "sha256": "1n47f7r8cm9pcsz7vl4nxjfvs0fgzvcmjda5h0inz3yx9vghp5xm", "type": "tarball", - "url": "https://github.com/input-output-hk/nix-bundle/archive/acf3b142c1fa332d4f9050e7cdfb3c3f8091e78b.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/b67e752c29f18a0ca5534a07661366d6a2c2e649.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "nixpkgs-nsis": { diff --git a/nix/sources.nix b/nix/sources.nix index 06a463f39f..1938409ddd 100644 --- a/nix/sources.nix +++ b/nix/sources.nix @@ -6,25 +6,33 @@ let # The fetchers. fetch_ fetches specs of type . # - fetch_file = pkgs: spec: - if spec.builtin or true then - builtins_fetchurl { inherit (spec) url sha256; } - else - pkgs.fetchurl { inherit (spec) url sha256; }; + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; fetch_tarball = pkgs: name: spec: let - ok = str: ! builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str); - # sanitize the name, though nix will still fail if name starts with period - name' = stringAsChars (x: if ! ok x then "-" else x) "${name}-src"; + name' = sanitizeName name + "-src"; in if spec.builtin or true then builtins_fetchTarball { name = name'; inherit (spec) url sha256; } else pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; - fetch_git = spec: - builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; + fetch_git = name: spec: + let + ref = + if spec ? ref then spec.ref else + if spec ? branch then "refs/heads/${spec.branch}" else + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; + in + builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; fetch_local = spec: spec.path; @@ -40,11 +48,21 @@ let # Various helpers # + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: + ( + concatMapStrings (s: if builtins.isList s then "-" else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + # The set of packages used when specs are fetched using non-builtins. - mkPkgs = sources: + mkPkgs = sources: system: let sourcesNixpkgs = - import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; hasThisAsNixpkgsPath = == ./.; in @@ -64,15 +82,27 @@ let if ! builtins.hasAttr "type" spec then abort "ERROR: niv spec ${name} does not have a 'type' attribute" - else if spec.type == "file" then fetch_file pkgs spec + else if spec.type == "file" then fetch_file pkgs name spec else if spec.type == "tarball" then fetch_tarball pkgs name spec - else if spec.type == "git" then fetch_git spec + else if spec.type == "git" then fetch_git name spec else if spec.type == "local" then fetch_local spec else if spec.type == "builtin-tarball" then fetch_builtin-tarball name else if spec.type == "builtin-url" then fetch_builtin-url name else abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + # Ports of functions for older nix versions # a Nix version of mapAttrs if the built-in doesn't exist @@ -89,25 +119,29 @@ let # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); concatStrings = builtins.concatStringsSep ""; + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: if cond then as else {}; + # fetchTarball version that is compatible between all the versions of Nix - builtins_fetchTarball = { url, name, sha256 }@attrs: + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: let inherit (builtins) lessThan nixVersion fetchTarball; in if lessThan nixVersion "1.12" then - fetchTarball { inherit name url; } + fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) else fetchTarball attrs; # fetchurl version that is compatible between all the versions of Nix - builtins_fetchurl = { url, sha256 }@attrs: + builtins_fetchurl = { url, name ? null, sha256 }@attrs: let inherit (builtins) lessThan nixVersion fetchurl; in if lessThan nixVersion "1.12" then - fetchurl { inherit url; } + fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) else fetchurl attrs; @@ -119,14 +153,15 @@ let then abort "The values in sources.json should not have an 'outPath' attribute" else - spec // { outPath = fetch config.pkgs name spec; } + spec // { outPath = replace name (fetch config.pkgs name spec); } ) config.sources; # The "config" used by the fetchers mkConfig = - { sourcesFile ? ./sources.json - , sources ? builtins.fromJSON (builtins.readFile sourcesFile) - , pkgs ? mkPkgs sources + { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null + , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system }: rec { # The sources, i.e. the attribute set of spec name to spec inherit sources; diff --git a/package.json b/package.json index efed01150d..0dde937bcb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "daedalus", "productName": "Daedalus", - "version": "4.5.2", + "version": "4.8.0", "description": "Cryptocurrency Wallet", "main": "./dist/main/index.js", "scripts": { @@ -12,6 +12,7 @@ "start:dev": "NODE_ENV=development gulp start", "dev": "IS_WATCH_MODE=true gulp dev", "test": "NODE_ENV=test yarn build && yarn test:unit && yarn test:e2e:fail-fast", + "test:jest": "NODE_OPTIONS=--experimental-vm-modules jest", "test:generate:report": "node tests/reporter.js", "test:unit": "yarn cucumber:run --require 'tests/**/unit/**/*.js' --tags '@unit and not @skip and not @wip'", "test:unit:rerun": "yarn cucumber:rerun --require 'tests/**/unit/**/*.js' --tags '@unit and not @skip and not @wip'", @@ -67,16 +68,17 @@ "electron": "./node_modules/.bin/electron" }, "devDependencies": { - "@babel/cli": "7.2.0", - "@babel/core": "7.4.5", - "@babel/helper-create-regexp-features-plugin": "7.7.0", + "@babel/cli": "7.16.0", + "@babel/core": "7.16.0", + "@babel/eslint-parser": "7.16.3", + "@babel/helper-create-regexp-features-plugin": "7.16.0", "@babel/plugin-proposal-class-properties": "7.2.1", - "@babel/plugin-proposal-decorators": "7.2.0", + "@babel/plugin-proposal-decorators": "7.16.4", "@babel/plugin-proposal-object-rest-spread": "7.2.0", "@babel/plugin-transform-runtime": "7.2.0", "@babel/polyfill": "7.0.0", - "@babel/preset-env": "7.2.0", - "@babel/preset-flow": "7.8.3", + "@babel/preset-env": "7.16.4", + "@babel/preset-flow": "7.16.0", "@babel/preset-react": "7.0.0", "@babel/register": "7.0.0", "@dump247/storybook-state": "1.6.1", @@ -86,15 +88,16 @@ "@storybook/addons": "5.3.14", "@storybook/core": "5.3.14", "@storybook/react": "5.3.14", + "@testing-library/jest-dom": "5.15.1", + "@testing-library/react": "12.1.2", "asar": "2.1.0", "autodll-webpack-plugin": "0.4.2", - "axios": "0.21.1", - "babel-eslint": "10.1.0", + "axios": "0.24.0", "babel-loader": "8.0.4", "babel-plugin-react-intl": "3.0.1", "bufferutil": "4.0.1", "cache-loader": "4.1.0", - "chai": "4.2.0", + "chai": "4.3.4", "chalk": "4.1.0", "concurrently": "5.3.0", "cross-env": "7.0.2", @@ -102,11 +105,11 @@ "cucumber": "6.0.5", "cucumber-pretty": "6.0.0", "del": "6.0.0", - "electron-chromedriver": "13.0.0", + "electron-chromedriver": "16.0.0", "electron-connect": "0.6.3", "electron-devtools-installer": "3.2.0", - "electron-packager": "15.2.0", - "electron-rebuild": "1.11.0", + "electron-packager": "15.4.0", + "electron-rebuild": "2.0.1", "eslint": "7.10.0", "eslint-config-airbnb": "18.2.0", "eslint-config-prettier": "6.12.0", @@ -114,6 +117,7 @@ "eslint-import-resolver-webpack": "0.13.1", "eslint-plugin-flowtype": "5.2.0", "eslint-plugin-import": "2.23.3", + "eslint-plugin-jest": "24.4.0", "eslint-plugin-jsx-a11y": "6.3.1", "eslint-plugin-promise": "4.2.1", "eslint-plugin-react": "7.21.2", @@ -128,8 +132,13 @@ "hash.js": "1.1.7", "html-loader": "0.5.5", "husky": "4.3.0", + "identity-obj-proxy": "3.0.0", + "jest": "26.6.3", + "jest-css-modules-transform": "4.3.0", + "jest-environment-jsdom": "26.6.2", + "jest-svg-transformer": "1.0.0", "markdown-loader": "5.1.0", - "mini-css-extract-plugin": "0.9.0", + "mini-css-extract-plugin": "0.12.0", "minimist": "1.2.5", "mobx-react-devtools": "6.1.1", "node-forge": "0.10.0", @@ -137,7 +146,7 @@ "node-sass": "4.14.1", "nodemon": "2.0.4", "npmlog": "4.1.2", - "postcss": "7.0.27", + "postcss": "7.0.39", "postcss-modules": "1.5.0", "prettier": "2.1.2", "pretty-quick": "3.0.2", @@ -146,8 +155,8 @@ "react-intl-translations-manager": "5.0.3", "react-syntax-highlighter": "13.5.3", "regenerator-runtime": "0.13.7", - "resolve-url": "0.2.1", "sass-loader": "7.1.0", + "seedrandom": "3.0.5", "sinon": "9.2.2", "spawn-sync": "2.0.0", "spectron": "14.0.0", @@ -156,6 +165,7 @@ "stylelint-order": "4.1.0", "svg-inline-loader": "0.8.2", "thread-loader": "2.1.3", + "timemachine": "0.3.2", "transform-loader": "0.2.4", "url-loader": "2.0.1", "utf-8-validate": "5.0.2", @@ -179,19 +189,19 @@ "borc": "2.1.2", "bs58": "4.0.1", "cardano-crypto.js": "5.3.6-rc.6", - "cardano-js": "0.4.7", - "cardano-launcher": "0.20210215.0", + "cardano-js": "0.4.8", + "cardano-launcher": "0.20211105.1", "cbor": "5.0.2", - "check-disk-space": "3.0.1", + "check-disk-space": "3.2.0", "chroma-js": "2.1.0", "classnames": "2.2.6", "csv-stringify": "5.5.1", "cucumber-html-reporter": "5.2.0", - "electron": "13.1.1", + "electron": "13.6.3", "electron-log-daedalus": "2.2.21", - "electron-store": "8.0.0", + "electron-store": "8.0.1", "es6-error": "4.1.1", - "find-process": "1.4.4", + "find-process": "1.4.7", "fireworks-js": "1.0.4", "form-data": "3.0.0", "fs-extra": "9.0.1", @@ -205,7 +215,7 @@ "lodash": "4.17.21", "lodash-es": "4.17.15", "mime-types": "2.1.27", - "mkdirp": "0.5.1", + "mkdirp": "0.5.5", "mobx": "5.15.7", "mobx-react": "6.3.0", "mobx-react-form": "2.0.8", @@ -245,9 +255,9 @@ "source-map-support": "0.5.19", "spectron-fake-dialog": "0.0.1", "tcp-port-used": "1.0.1", - "trezor-connect": "8.2.0-extended", + "trezor-connect": "8.2.4-extended", "unorm": "1.6.0", - "validator": "13.1.17" + "validator": "13.7.0" }, "devEngines": { "node": ">=14.17.0", diff --git a/shell.nix b/shell.nix index bcb3c3f3fa..8b6d2cc009 100644 --- a/shell.nix +++ b/shell.nix @@ -2,7 +2,7 @@ , config ? {} , nodeImplementation ? "cardano" , localLib ? import ./lib.nix { inherit nodeImplementation; } -, pkgs ? localLib.iohkNix.getPkgs { inherit system config; } +, pkgs ? import (import ./nix/sources.nix).nixpkgs { inherit system config; } , cluster ? "selfnode" , systemStart ? null , autoStartBackend ? systemStart != null diff --git a/source/common/config/electron-store.config.js b/source/common/config/electron-store.config.js index 81cfc623e9..c18b83d727 100644 --- a/source/common/config/electron-store.config.js +++ b/source/common/config/electron-store.config.js @@ -20,9 +20,12 @@ export const STORAGE_KEYS: { CURRENCY_ACTIVE: 'CURRENCY-ACTIVE', CURRENCY_SELECTED: 'CURRENCY-SELECTED', DATA_LAYER_MIGRATION_ACCEPTANCE: 'DATA-LAYER-MIGRATION-ACCEPTANCE', + DISCREET_MODE_ENABLED: 'DISCREET-MODE-ENABLED', + DISCREET_MODE_SETTINGS_TOOLTIP: 'DISCREET-MODE-SETTINGS-TOOLTIP', + DISCREET_MODE_NOTIFICATION: 'DISCREET-MODE-NOTIFICATION', DOWNLOAD_MANAGER: 'DOWNLOAD-MANAGER', - HARDWARE_WALLET_DEVICES: 'HARDWARE-WALLET-DEVICES', HARDWARE_WALLETS: 'HARDWARE-WALLETS', + HARDWARE_WALLET_DEVICES: 'HARDWARE-WALLET-DEVICES', READ_NEWS: 'READ-NEWS', RESET: 'RESET', SMASH_SERVER: 'SMASH-SERVER', @@ -35,7 +38,7 @@ export const STORAGE_KEYS: { USER_LOCALE: 'USER-LOCALE', USER_NUMBER_FORMAT: 'USER-NUMBER-FORMAT', USER_TIME_FORMAT: 'USER-TIME-FORMAT', - WALLET_MIGRATION_STATUS: 'WALLET-MIGRATION-STATUS', WALLETS: 'WALLETS', + WALLET_MIGRATION_STATUS: 'WALLET-MIGRATION-STATUS', WINDOW_BOUNDS: 'WINDOW-BOUNDS', }; diff --git a/source/common/ipc/api.js b/source/common/ipc/api.js index e35eec6df1..180478e6ef 100644 --- a/source/common/ipc/api.js +++ b/source/common/ipc/api.js @@ -19,6 +19,7 @@ import type { CardanoStatus, FaultInjectionIpcRequest, TlsConfig, + BlockSyncType, } from '../types/cardano-node.types'; import type { CheckDiskSpaceResponse } from '../types/no-disk-space.types'; import type { LogFiles } from '../../renderer/app/types/LogTypes'; @@ -453,9 +454,13 @@ export type IntrospectAddressMainResponse = IntrospectAddressResponse; /** * Channel for checking block replay progress */ -export const GET_BLOCK_REPLAY_STATUS_CHANNEL = 'GetBlockReplayProgressChannel'; -export type GetBlockReplayProgressRendererRequest = void; -export type GetBlockReplayProgressMainResponse = number; +export const GET_BLOCK_SYNC_STATUS_CHANNEL = 'GetBlockSyncProgressChannel'; +export type GetBlockSyncProgressType = BlockSyncType; +export type GetBlockSyncProgressRendererRequest = void; +export type GetBlockSyncProgressMainResponse = { + progress: number, + type: GetBlockSyncProgressType, +}; /** * Channels for connecting / interacting with Hardware Wallet devices @@ -514,3 +519,7 @@ export type deriveAddressMainResponse = string; export const SHOW_ADDRESS_CHANNEL = 'SHOW_ADDRESS_CHANNEL'; export type showAddressRendererRequest = showAddressRendererRequestType; export type showAddressMainResponse = void; + +export const TOGGLE_RTS_FLAGS_MODE_CHANNEL = 'TOGGLE_RTS_FLAGS_MODE_CHANNEL'; +export type ToggleRTSFlagsModeRendererRequest = void; +export type ToggleRTSFlagsModeMainResponse = void; diff --git a/source/common/ipc/constants.js b/source/common/ipc/constants.js index 18e4d5c834..1947f07b9f 100644 --- a/source/common/ipc/constants.js +++ b/source/common/ipc/constants.js @@ -3,6 +3,7 @@ export const DIALOGS = { ABOUT: 'ABOUT_DIALOG', DAEDALUS_DIAGNOSTICS: 'DAEDALUS_DIAGNOSTICS_DIALOG', ITN_REWARDS_REDEMPTION: 'ITN_REWARDS_REDEMPTION_DIALOG', + TOGGLE_RTS_FLAGS_MODE: 'TOGGLE_RTS_FLAGS_MODE_DIALOG', }; export const NOTIFICATIONS = { diff --git a/source/common/types/address-introspection.types.js b/source/common/types/address-introspection.types.js index 80b45568dd..86f96d93a8 100644 --- a/source/common/types/address-introspection.types.js +++ b/source/common/types/address-introspection.types.js @@ -4,7 +4,9 @@ export type IntrospectAddressRequest = { input: string, }; -export type AddressStyle = 'Byron' | 'Icarus' | 'Jormungandr' | 'Shelley'; +export type AddressStyle = 'Byron' | 'Icarus' | 'Shelley'; + +export type AddressType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15; export type ChainPointer = { slot_num: number, @@ -13,6 +15,7 @@ export type ChainPointer = { }; export type AddressBase = { + address_type: AddressType, address_style: AddressStyle, network_tag: number | null, stake_reference: 'none' | 'by pointer' | 'by value', @@ -27,14 +30,6 @@ export type IcarusAddress = AddressBase & { address_root: string, }; -export type JormungandrAddress = AddressBase & { - address_type: 'single' | 'group' | 'account' | 'multisig', - account_key?: string, - merkle_root?: string, - spending_key?: string, - stake_key?: string, -}; - export type ShelleyAddress = AddressBase & { pointer?: ChainPointer, script_hash?: string, @@ -45,10 +40,6 @@ export type ShelleyAddress = AddressBase & { export type IntrospectAddressResponse = | { - introspection: - | ByronAddress - | IcarusAddress - | JormungandrAddress - | ShelleyAddress, + introspection: ByronAddress | IcarusAddress | ShelleyAddress, } | 'Invalid'; diff --git a/source/common/types/cardano-node.types.js b/source/common/types/cardano-node.types.js index 10082a3758..89e4c32dd6 100644 --- a/source/common/types/cardano-node.types.js +++ b/source/common/types/cardano-node.types.js @@ -149,6 +149,7 @@ export type CardanoStatus = { hasBeenConnected: boolean, cardanoNodePID: number, cardanoWalletPID: number, + isRTSFlagsModeEnabled: boolean, }; export type NetworkMagicType = Array; @@ -174,3 +175,8 @@ export const NetworkMagics: { // Cardano Selfnode network magic [SELFNODE]: [1, null], }; + +export type BlockSyncType = + | 'validatingChunk' + | 'pushingLedger' + | 'replayedBlock'; diff --git a/source/common/types/electron-store.types.js b/source/common/types/electron-store.types.js index 6cfa89c19c..aa15fbdfa7 100644 --- a/source/common/types/electron-store.types.js +++ b/source/common/types/electron-store.types.js @@ -11,6 +11,9 @@ export type StorageKey = | 'CURRENCY-ACTIVE' | 'CURRENCY-SELECTED' | 'DATA-LAYER-MIGRATION-ACCEPTANCE' + | 'DISCREET-MODE-ENABLED' + | 'DISCREET-MODE-SETTINGS-TOOLTIP' + | 'DISCREET-MODE-NOTIFICATION' | 'DOWNLOAD-MANAGER' | 'HARDWARE-WALLET-DEVICES' | 'HARDWARE-WALLETS' diff --git a/source/common/types/environment.types.js b/source/common/types/environment.types.js index 218b9b2782..bdf76ab708 100644 --- a/source/common/types/environment.types.js +++ b/source/common/types/environment.types.js @@ -25,6 +25,7 @@ export type Environment = { os: string, cpu: string, ram: number, + hasMetHardwareRequirements: boolean, installerVersion: string, version: string, isWindows: boolean, diff --git a/source/common/types/no-disk-space.types.js b/source/common/types/no-disk-space.types.js index 5c27907b72..3ab600a052 100644 --- a/source/common/types/no-disk-space.types.js +++ b/source/common/types/no-disk-space.types.js @@ -1,5 +1,4 @@ // @flow - export type CheckDiskSpaceResponse = { isNotEnoughDiskSpace: boolean, diskSpaceRequired: string, diff --git a/source/main/cardano/CardanoNode.js b/source/main/cardano/CardanoNode.js index c32a2f3f08..c6c31d9475 100644 --- a/source/main/cardano/CardanoNode.js +++ b/source/main/cardano/CardanoNode.js @@ -30,6 +30,7 @@ import { CardanoSelfnodeLauncher } from './CardanoSelfnodeLauncher'; import { launcherConfig } from '../config'; import type { NodeConfig } from '../config'; import type { Logger } from '../../common/types/logging.types'; +import { containsRTSFlags } from '../utils/containsRTSFlags'; /* eslint-disable consistent-return */ @@ -77,6 +78,7 @@ export type CardanoNodeConfig = { cliBin: string, // Path to cardano-cli executable isStaging: boolean, metadataUrl?: string, + rtsFlags: Array, }; const CARDANO_UPDATE_EXIT_CODE = 20; @@ -224,6 +226,7 @@ export class CardanoNode { return Object.assign({}, this._status, { cardanoNodePID: get(this, '_node.pid', 0), cardanoWalletPID: get(this, '_node.wpid', 0), + isRTSFlagsModeEnabled: containsRTSFlags(this._config.rtsFlags), }); } diff --git a/source/main/cardano/CardanoWalletLauncher.js b/source/main/cardano/CardanoWalletLauncher.js index ce10a464d7..1498b19810 100644 --- a/source/main/cardano/CardanoWalletLauncher.js +++ b/source/main/cardano/CardanoWalletLauncher.js @@ -36,6 +36,7 @@ export type WalletOptions = { cliBin: string, isStaging: boolean, metadataUrl?: string, + rtsFlags: Array, }; export async function CardanoWalletLauncher( @@ -54,6 +55,7 @@ export async function CardanoWalletLauncher( cliBin, isStaging, metadataUrl, + rtsFlags, } = walletOptions; // TODO: Update launcher config to pass number const syncToleranceSeconds = parseInt(syncTolerance.replace('s', ''), 10); @@ -138,6 +140,19 @@ export async function CardanoWalletLauncher( logger.info('Launching Wallet with --token-metadata-server flag', { tokenMetadataServer, }); + + // RTS flags: + // 1) "-H4G -M6553M -c70" 16.0% peak RSS reduction and a sub-percentile CPU regression + // 2) "-H4G -M6553M" 18.5% peak RSS reduction and a second-best CPU regression + if (!!rtsFlags && rtsFlags?.length > 0) { + nodeConfig.rtsOpts = rtsFlags; + logger.info('Launching Cardano Node with RTS flags', { + rtsFlags, + }); + } else { + logger.info('Launching Cardano Node without RTS flags'); + } + merge(launcherConfig, { nodeConfig, tlsConfiguration, diff --git a/source/main/cardano/setup.js b/source/main/cardano/setup.js index 6028e680e5..28c77821ab 100644 --- a/source/main/cardano/setup.js +++ b/source/main/cardano/setup.js @@ -48,10 +48,12 @@ const restartCardanoNode = async (node: CardanoNode) => { * * @param launcherConfig {LauncherConfig} * @param mainWindow + * @param rtsFlags flags used to start cardano-node */ export const setupCardanoNode = ( launcherConfig: LauncherConfig, - mainWindow: BrowserWindow + mainWindow: BrowserWindow, + rtsFlags: Array ): CardanoNode => { const { logsPrefix, @@ -77,6 +79,7 @@ export const setupCardanoNode = ( configPath, syncTolerance, cliBin, + rtsFlags, isStaging, metadataUrl, startupTimeout: NODE_STARTUP_TIMEOUT, diff --git a/source/main/config.js b/source/main/config.js index b3133ba471..20a57a1f16 100644 --- a/source/main/config.js +++ b/source/main/config.js @@ -51,6 +51,7 @@ export type NodeConfig = { topologyFile: string, }, signingKey?: string, + rtsOpts?: Array, }; /** @@ -176,3 +177,5 @@ export const FALLBACK_TOKEN_METADATA_SERVER_URL = export const MOCK_TOKEN_METADATA_SERVER_URL = 'http://localhost'; export const MOCK_TOKEN_METADATA_SERVER_PORT = process.env.MOCK_TOKEN_METADATA_SERVER_PORT || 0; + +export const RTS_FLAGS = ['-c']; diff --git a/source/main/environment.js b/source/main/environment.js index 18ae6e63b9..c0eb014b78 100644 --- a/source/main/environment.js +++ b/source/main/environment.js @@ -21,6 +21,13 @@ import { checkIsLinux, } from '../common/utils/environmentCheckers'; +// Daedalus requires minimum 16 gigabytes of RAM, but some devices having 16 GB +// actually have a slightly smaller RAM size (eg. 15.99 GB), therefore we used 15 GB threshold +// +// TODO figure out better place for it - can't import from config.js as it would be a circular dep +// https://input-output.atlassian.net/browse/DDW-928 +export const RECOMMENDED_RAM_IN_BYTES = 15 * 1024 * 1024 * 1024; + /* ================================================================== = Evaluations = ================================================================== */ @@ -41,7 +48,7 @@ const isDevelopment = checkIsDevelopment(NETWORK); const isWatchMode = process.env.IS_WATCH_MODE; const keepLocalClusterRunning = process.env.KEEP_LOCAL_CLUSTER_RUNNING; const API_VERSION = process.env.API_VERSION || 'dev'; -const NODE_VERSION = '1.30.1'; // TODO: pick up this value from process.env +const NODE_VERSION = '1.33.0'; // TODO: pick up this value from process.env const mainProcessID = get(process, 'ppid', '-'); const rendererProcessID = process.pid; const PLATFORM = os.platform(); @@ -49,6 +56,7 @@ const PLATFORM_VERSION = os.release(); const OS = OS_NAMES[PLATFORM] || PLATFORM; const cpu = os.cpus(); const ram = os.totalmem(); +const hasMetHardwareRequirements = ram >= RECOMMENDED_RAM_IN_BYTES; const isBlankScreenFixActive = includes(process.argv.slice(1), '--safe-mode'); const BUILD = process.env.BUILD_NUMBER || 'dev'; const BUILD_NUMBER = uniq([API_VERSION, BUILD]).join('.'); @@ -97,6 +105,7 @@ export const environment: Environment = Object.assign( isLinux, isBlankScreenFixActive, keepLocalClusterRunning, + hasMetHardwareRequirements, }, process.env ); diff --git a/source/main/index.js b/source/main/index.js index 18fab65c15..8a6e26cfbc 100644 --- a/source/main/index.js +++ b/source/main/index.js @@ -2,6 +2,7 @@ import os from 'os'; import path from 'path'; import { app, dialog, BrowserWindow, screen, shell } from 'electron'; +import type { Event } from 'electron'; import { client } from 'electron-connect'; import EventEmitter from 'events'; import { requestElectronStore } from './ipc/electronStoreConversation'; @@ -21,6 +22,7 @@ import mainErrorHandler from './utils/mainErrorHandler'; import { launcherConfig, pubLogsFolderPath, + RTS_FLAGS, stateDirectoryPath, } from './config'; import { setupCardanoNode } from './cardano/setup'; @@ -48,6 +50,12 @@ import { restoreSavedWindowBounds, saveWindowBoundsOnSizeAndPositionChange, } from './windows/windowBounds'; +import { + getRtsFlagsSettings, + storeRtsFlagsSettings, +} from './utils/rtsFlagsSettings'; +import { toggleRTSFlagsModeChannel } from './ipc/toggleRTSFlagsModeChannel'; +import { containsRTSFlags } from './utils/containsRTSFlags'; /* eslint-disable consistent-return */ @@ -107,6 +115,12 @@ const safeExit = async () => { } }; +const handleWindowClose = async (event: ?Event) => { + logger.info('mainWindow received event. Safe exiting Daedalus now.'); + event?.preventDefault(); + await safeExit(); +}; + const onAppReady = async () => { setupLogging(); logUsedVersion( @@ -117,6 +131,7 @@ const onAppReady = async () => { const cpu = os.cpus(); const platformVersion = os.release(); const ram = JSON.stringify(os.totalmem(), null, 2); + const startTime = new Date().toISOString(); // first checks for Japanese locale, otherwise returns english const systemLocale = detectSystemLocale(); @@ -162,8 +177,12 @@ const onAppReady = async () => { ); saveWindowBoundsOnSizeAndPositionChange(mainWindow, requestElectronStore); - logger.info('Setting up Cardano Node...'); - cardanoNode = setupCardanoNode(launcherConfig, mainWindow); + const currentRtsFlags = getRtsFlagsSettings(network) || []; + + logger.info( + `Setting up Cardano Node... with flags: ${JSON.stringify(currentRtsFlags)}` + ); + cardanoNode = setupCardanoNode(launcherConfig, mainWindow, currentRtsFlags); buildAppMenus(mainWindow, cardanoNode, userLocale, { isNavigationEnabled: false, @@ -172,7 +191,8 @@ const onAppReady = async () => { enableApplicationMenuNavigationChannel.onReceive( () => new Promise((resolve) => { - buildAppMenus(mainWindow, cardanoNode, userLocale, { + const locale = getLocale(network); + buildAppMenus(mainWindow, cardanoNode, locale, { isNavigationEnabled: true, }); resolve(); @@ -182,10 +202,11 @@ const onAppReady = async () => { rebuildApplicationMenu.onReceive( (data) => new Promise((resolve) => { - buildAppMenus(mainWindow, cardanoNode, userLocale, { + const locale = getLocale(network); + buildAppMenus(mainWindow, cardanoNode, locale, { isNavigationEnabled: data.isNavigationEnabled, }); - mainWindow.updateTitle(userLocale); + mainWindow.updateTitle(locale); resolve(); }) ); @@ -212,6 +233,12 @@ const onAppReady = async () => { getSystemLocaleChannel.onRequest(() => Promise.resolve(systemLocale)); + toggleRTSFlagsModeChannel.onReceive(() => { + const flagsToSet = containsRTSFlags(currentRtsFlags) ? [] : RTS_FLAGS; + storeRtsFlagsSettings(environment.network, flagsToSet); + return handleWindowClose(); + }); + const handleCheckDiskSpace = handleDiskSpace(mainWindow, cardanoNode); const onMainError = (error: string) => { if (error.indexOf('ENOSPC') > -1) { @@ -220,21 +247,15 @@ const onAppReady = async () => { } }; mainErrorHandler(onMainError); - await handleCheckDiskSpace(); await handleCheckBlockReplayProgress(mainWindow, launcherConfig.logsPrefix); + await handleCheckDiskSpace(); if (isWatchMode) { // Connect to electron-connect server which restarts / reloads windows on file changes client.create(mainWindow); } - mainWindow.on('close', async (event) => { - logger.info( - 'mainWindow received event. Safe exiting Daedalus now.' - ); - event.preventDefault(); - await safeExit(); - }); + mainWindow.on('close', handleWindowClose); // Security feature: Prevent creation of new browser windows // https://github.com/electron/electron/blob/master/docs/tutorial/security.md#14-disable-or-limit-creation-of-new-windows diff --git a/source/main/ipc/electronStoreConversation.js b/source/main/ipc/electronStoreConversation.js index ab4a2547ba..748205e7c9 100644 --- a/source/main/ipc/electronStoreConversation.js +++ b/source/main/ipc/electronStoreConversation.js @@ -32,6 +32,9 @@ const reset = async () => { await unset(keys.CURRENCY_ACTIVE); await unset(keys.CURRENCY_SELECTED); await unset(keys.DATA_LAYER_MIGRATION_ACCEPTANCE); + await unset(keys.DISCREET_MODE_ENABLED); + await unset(keys.DISCREET_MODE_SETTINGS_TOOLTIP); + await unset(keys.DISCREET_MODE_NOTIFICATION); await unset(keys.DOWNLOAD_MANAGER); await unset(keys.HARDWARE_WALLET_DEVICES); await unset(keys.HARDWARE_WALLETS); diff --git a/source/main/ipc/get-block-replay-progress.js b/source/main/ipc/get-block-replay-progress.js deleted file mode 100644 index 0f33d4bb73..0000000000 --- a/source/main/ipc/get-block-replay-progress.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import { MainIpcChannel } from './lib/MainIpcChannel'; -import { GET_BLOCK_REPLAY_STATUS_CHANNEL } from '../../common/ipc/api'; -import type { - GetBlockReplayProgressRendererRequest, - GetBlockReplayProgressMainResponse, -} from '../../common/ipc/api'; - -// IpcChannel - -export const getBlockReplayProgressChannel: MainIpcChannel< - GetBlockReplayProgressRendererRequest, - GetBlockReplayProgressMainResponse -> = new MainIpcChannel(GET_BLOCK_REPLAY_STATUS_CHANNEL); diff --git a/source/main/ipc/get-block-sync-progress.js b/source/main/ipc/get-block-sync-progress.js new file mode 100644 index 0000000000..03e594eecc --- /dev/null +++ b/source/main/ipc/get-block-sync-progress.js @@ -0,0 +1,14 @@ +// @flow +import { MainIpcChannel } from './lib/MainIpcChannel'; +import { GET_BLOCK_SYNC_STATUS_CHANNEL } from '../../common/ipc/api'; +import type { + GetBlockSyncProgressRendererRequest, + GetBlockSyncProgressMainResponse, +} from '../../common/ipc/api'; + +// IpcChannel + +export const getBlockSyncProgressChannel: MainIpcChannel< + GetBlockSyncProgressRendererRequest, + GetBlockSyncProgressMainResponse +> = new MainIpcChannel(GET_BLOCK_SYNC_STATUS_CHANNEL); diff --git a/source/main/ipc/toggleRTSFlagsModeChannel.js b/source/main/ipc/toggleRTSFlagsModeChannel.js new file mode 100644 index 0000000000..4cd2a65f15 --- /dev/null +++ b/source/main/ipc/toggleRTSFlagsModeChannel.js @@ -0,0 +1,12 @@ +// @flow +import { TOGGLE_RTS_FLAGS_MODE_CHANNEL } from '../../common/ipc/api'; +import type { + ToggleRTSFlagsModeMainResponse, + ToggleRTSFlagsModeRendererRequest, +} from '../../common/ipc/api'; +import { MainIpcChannel } from './lib/MainIpcChannel'; + +export const toggleRTSFlagsModeChannel: MainIpcChannel< + ToggleRTSFlagsModeRendererRequest, + ToggleRTSFlagsModeMainResponse +> = new MainIpcChannel(TOGGLE_RTS_FLAGS_MODE_CHANNEL); diff --git a/source/main/locales/en-US.json b/source/main/locales/en-US.json index 977b539f85..7fdc70661b 100644 --- a/source/main/locales/en-US.json +++ b/source/main/locales/en-US.json @@ -22,6 +22,13 @@ "menu.edit.undo": "Undo", "menu.helpSupport": "Help", "menu.helpSupport.blankScreenFix": "Blank Screen Fix", + "menu.helpSupport.usingRtsFlags": "Using RTS flags", + "menu.helpSupport.rtsFlagsDialogCancel": "Cancel", + "menu.helpSupport.rtsFlagsDialogConfirm": "Yes", + "menu.helpSupport.enableRtsFlagsDialogMessage": "When enabled, the Cardano node will attempt to reduce its RAM usage", + "menu.helpSupport.enableRtsFlagsDialogTitle": "Enable RAM management (RTS Flags)", + "menu.helpSupport.disableRtsFlagsDialogMessage": "When disabled, we will restart cardano-node in default mode", + "menu.helpSupport.disableRtsFlagsDialogTitle": "Disable RAM management (RTS Flags)", "menu.helpSupport.blankScreenFixDialogCancel": "Cancel", "menu.helpSupport.blankScreenFixDialogConfirm": "Yes", "menu.helpSupport.blankScreenFixDialogMessage": "Turn off 'Blank screen fix'? \n \nDisabling the blank screen fix setting will improve the performance of user interface rendering by enabling graphics acceleration, however, some users may find that Daedalus runs better with this setting enabled. If you see a blank screen instead of the Daedalus user interface after disabling this setting and restarting Daedalus, please turn this setting back on. \n \nDo you want to disable this setting and restart Daedalus?", @@ -43,5 +50,6 @@ "menu.view.toggleDeveloperTools": "Toggle Developer Tools", "menu.view.toggleFullScreen": "Toggle Full Screen", "menu.view.toggleMaximumWindowSize": "Toggle Maximum Window Size", - "window.title.blankScreenFix": "['Blank screen fix' active]" + "window.title.blankScreenFix": "['Blank screen fix' active]", + "window.title.usingRtsFlags": "['Using RTS flags' active]" } diff --git a/source/main/locales/ja-JP.json b/source/main/locales/ja-JP.json index c681f2dd91..e153ae0042 100644 --- a/source/main/locales/ja-JP.json +++ b/source/main/locales/ja-JP.json @@ -22,6 +22,13 @@ "menu.edit.undo": "元に戻す", "menu.helpSupport": "ヘルプ", "menu.helpSupport.blankScreenFix": "ブランク画面修正", + "menu.helpSupport.usingRtsFlags": "RTSフラグの使用", + "menu.helpSupport.rtsFlagsDialogCancel": "Cancel", + "menu.helpSupport.rtsFlagsDialogConfirm": "Yes", + "menu.helpSupport.enableRtsFlagsDialogMessage": "When enabled, the Cardano node will attempt to reduce its RAM usage", + "menu.helpSupport.enableRtsFlagsDialogTitle": "Enable RAM management (RTSフラグの使用\")", + "menu.helpSupport.disableRtsFlagsDialogMessage": "When disabled, we will restart cardano-node in default mode", + "menu.helpSupport.disableRtsFlagsDialogTitle": "Disable RAM management (RTSフラグの使用\")", "menu.helpSupport.blankScreenFixDialogCancel": "キャンセル", "menu.helpSupport.blankScreenFixDialogConfirm": "はい", "menu.helpSupport.blankScreenFixDialogMessage": "「ブランク画面修正」を無効にしますか? \n \nブランク画面修正設定を無効にすると、グラフィックアクセラレーションが有効化されてユーザーインターフェイスのレンダリングパフォーマンスが向上しますが、この設定を有効にした方がDaedalusがスムーズに作動する場合があります。この設定を無効にしてDaedalusを再起動した際にDaedalusユーザーインターフェイスの代わりにブランク画面が表示される場合は、この設定をもう一度有効にしてください。 \n \nこの設定を無効にしてDaedalusを再起動しますか。", @@ -43,5 +50,6 @@ "menu.view.toggleDeveloperTools": "開発者ツール切替え", "menu.view.toggleFullScreen": "フルスクリーン切替え", "menu.view.toggleMaximumWindowSize": "最大ウインドウサイズ切り切り替え", - "window.title.blankScreenFix": "[「ブランク画面修正」有効 ]" + "window.title.blankScreenFix": "[「ブランク画面修正」有効 ]", + "window.title.usingRtsFlags": "[「RTSフラグの使用」が有効]" } diff --git a/source/main/menus/MenuActions.types.js b/source/main/menus/MenuActions.types.js index d401692d6a..8662a5f5ed 100644 --- a/source/main/menus/MenuActions.types.js +++ b/source/main/menus/MenuActions.types.js @@ -1,6 +1,7 @@ // @flow export type MenuActions = { toggleBlankScreenFix: Function, + openToggleRTSFlagsModeDialog: Function, openAboutDialog: Function, openDaedalusDiagnosticsDialog: Function, openItnRewardsRedemptionDialog: Function, diff --git a/source/main/menus/osx.js b/source/main/menus/osx.js index 7ce0f6d8b6..e6b243e2c3 100644 --- a/source/main/menus/osx.js +++ b/source/main/menus/osx.js @@ -8,9 +8,9 @@ import { environment } from '../environment'; import { showUiPartChannel } from '../ipc/control-ui-parts'; import { NOTIFICATIONS } from '../../common/ipc/constants'; import { generateSupportRequestLink } from '../../common/utils/reporting'; +import { buildKnownIssueFixesSubmenu } from './submenuBuilders'; const id = 'menu'; -const { isBlankScreenFixActive } = environment; export const osxMenu = ( app: App, @@ -139,21 +139,7 @@ export const osxMenu = ( { label: translation('helpSupport'), submenu: compact([ - { - label: translation('helpSupport.knownIssues'), - click() { - const faqLink = translation('helpSupport.knownIssuesUrl'); - shell.openExternal(faqLink); - }, - }, - { - label: translation('helpSupport.blankScreenFix'), - type: 'checkbox', - checked: isBlankScreenFixActive, - click(item) { - actions.toggleBlankScreenFix(item); - }, - }, + ...buildKnownIssueFixesSubmenu(actions, translations, translation), { type: 'separator' }, { label: translation('helpSupport.safetyTips'), @@ -162,15 +148,6 @@ export const osxMenu = ( shell.openExternal(safetyTipsLinkUrl); }, }, - /* { - label: translation('helpSupport.featureRequest'), - click() { - const featureRequestLinkUrl = translation( - 'helpSupport.featureRequestUrl' - ); - shell.openExternal(featureRequestLinkUrl); - }, - }, */ { label: translation('helpSupport.supportRequest'), click() { diff --git a/source/main/menus/submenuBuilders.js b/source/main/menus/submenuBuilders.js new file mode 100644 index 0000000000..09f7d907bd --- /dev/null +++ b/source/main/menus/submenuBuilders.js @@ -0,0 +1,45 @@ +// @flow +import { shell } from 'electron'; +import type { MenuItem } from 'electron'; +import { getRtsFlagsSettings } from '../utils/rtsFlagsSettings'; +import { environment } from '../environment'; +import type { MenuActions } from './MenuActions.types'; +import { getTranslation } from '../utils/getTranslation'; + +export const buildKnownIssueFixesSubmenu = ( + actions: MenuActions, + translations: {}, + translate: Function = getTranslation(translations, 'menu') +): MenuItem[] => { + const { isBlankScreenFixActive, network } = environment; + const rtsFlags = getRtsFlagsSettings(network); + const areRTSFlagsEnabled = !!rtsFlags?.length && rtsFlags.length > 0; + + return [ + { + label: translate('helpSupport.knownIssues'), + click() { + const faqLink = translate('helpSupport.knownIssuesUrl'); + shell.openExternal(faqLink); + }, + }, + { + label: translate('helpSupport.blankScreenFix'), + type: 'checkbox', + checked: isBlankScreenFixActive, + click(item) { + actions.toggleBlankScreenFix(item); + }, + }, + { + label: translate('helpSupport.usingRtsFlags'), + type: 'checkbox', + checked: areRTSFlagsEnabled, + click(item) { + actions.openToggleRTSFlagsModeDialog(!areRTSFlagsEnabled); + // keep previous setting until app restart + item.checked = areRTSFlagsEnabled; + }, + }, + ]; +}; diff --git a/source/main/menus/win-linux.js b/source/main/menus/win-linux.js index b3b3aebcdf..c1e5a0cfbd 100644 --- a/source/main/menus/win-linux.js +++ b/source/main/menus/win-linux.js @@ -8,9 +8,9 @@ import { environment } from '../environment'; import { NOTIFICATIONS } from '../../common/ipc/constants'; import { showUiPartChannel } from '../ipc/control-ui-parts'; import { generateSupportRequestLink } from '../../common/utils/reporting'; +import { buildKnownIssueFixesSubmenu } from './submenuBuilders'; const id = 'menu'; -const { isWindows, isBlankScreenFixActive } = environment; export const winLinuxMenu = ( app: App, @@ -120,7 +120,7 @@ export const winLinuxMenu = ( { type: 'separator', }, - isWindows + environment.isWindows ? { label: translation('view.toggleFullScreen'), accelerator: 'F11', @@ -151,21 +151,7 @@ export const winLinuxMenu = ( { label: translation('helpSupport'), submenu: compact([ - { - label: translation('helpSupport.knownIssues'), - click() { - const faqLink = translation('helpSupport.knownIssuesUrl'); - shell.openExternal(faqLink); - }, - }, - { - label: translation('helpSupport.blankScreenFix'), - type: 'checkbox', - checked: isBlankScreenFixActive, - click(item) { - actions.toggleBlankScreenFix(item); - }, - }, + ...buildKnownIssueFixesSubmenu(actions, translations, translation), { type: 'separator' }, { label: translation('helpSupport.safetyTips'), diff --git a/source/main/utils/buildAppMenus.js b/source/main/utils/buildAppMenus.js index 79be8bce92..c24013c148 100644 --- a/source/main/utils/buildAppMenus.js +++ b/source/main/utils/buildAppMenus.js @@ -1,5 +1,5 @@ // @flow -import { app, globalShortcut, Menu, BrowserWindow, dialog } from 'electron'; +import { app, BrowserWindow, dialog, globalShortcut, Menu } from 'electron'; import { environment } from '../environment'; import { winLinuxMenu } from '../menus/win-linux'; import { osxMenu } from '../menus/osx'; @@ -18,7 +18,12 @@ export const buildAppMenus = async ( isNavigationEnabled: boolean, } ) => { - const { ABOUT, DAEDALUS_DIAGNOSTICS, ITN_REWARDS_REDEMPTION } = DIALOGS; + const { + ABOUT, + DAEDALUS_DIAGNOSTICS, + ITN_REWARDS_REDEMPTION, + TOGGLE_RTS_FLAGS_MODE, + } = DIALOGS; const { SETTINGS, WALLET_SETTINGS } = PAGES; const { isNavigationEnabled } = data; @@ -92,6 +97,10 @@ export const buildAppMenus = async ( item.checked = isBlankScreenFixActive; }; + const openToggleRTSFlagsModeDialog = () => { + if (mainWindow) showUiPartChannel.send(TOGGLE_RTS_FLAGS_MODE, mainWindow); + }; + const menuActions = { openAboutDialog, openDaedalusDiagnosticsDialog, @@ -99,6 +108,7 @@ export const buildAppMenus = async ( openSettingsPage, openWalletSettingsPage, toggleBlankScreenFix, + openToggleRTSFlagsModeDialog, }; // Build app menus diff --git a/source/main/utils/containsRTSFlags.js b/source/main/utils/containsRTSFlags.js new file mode 100644 index 0000000000..a2200d5a4a --- /dev/null +++ b/source/main/utils/containsRTSFlags.js @@ -0,0 +1,5 @@ +// @flow +import { isEqual } from 'lodash'; +import { RTS_FLAGS } from '../config'; + +export const containsRTSFlags = (flags: string[]) => isEqual(flags, RTS_FLAGS); diff --git a/source/main/utils/detectSystemLocale.js b/source/main/utils/detectSystemLocale.js index 9ee803c506..727b2fff84 100644 --- a/source/main/utils/detectSystemLocale.js +++ b/source/main/utils/detectSystemLocale.js @@ -1,10 +1,12 @@ // @flow import { app } from 'electron'; +import { logger } from './logging'; import { LOCALES } from '../../common/types/locales.types.js'; import type { Locale } from '../../common/types/locales.types.js'; export const detectSystemLocale = (): Locale => { const systemLocale = app.getLocale(); + logger.info('Detected system locale', { systemLocale }); if (systemLocale === 'ja') { return LOCALES.japanese; } diff --git a/source/main/utils/handleCheckBlockReplayProgress.js b/source/main/utils/handleCheckBlockReplayProgress.js index 4e08aefaad..d8a53b4762 100644 --- a/source/main/utils/handleCheckBlockReplayProgress.js +++ b/source/main/utils/handleCheckBlockReplayProgress.js @@ -3,9 +3,47 @@ import { BrowserWindow } from 'electron'; import fs from 'fs'; import readline from 'readline'; import path from 'path'; -import { getBlockReplayProgressChannel } from '../ipc/get-block-replay-progress'; +import { getBlockSyncProgressChannel } from '../ipc/get-block-sync-progress'; +import type { GetBlockSyncProgressType } from '../../common/ipc/api'; import { BLOCK_REPLAY_PROGRESS_CHECK_INTERVAL } from '../config'; +const blockKeyword = 'Replayed block'; +const validatingChunkKeyword = 'Validating chunk'; +const validatedChunkKeyword = 'Validated chunk'; +const ledgerKeyword = 'Pushing ledger state'; + +const progressKeywords = [ + blockKeyword, + validatingChunkKeyword, + validatedChunkKeyword, + ledgerKeyword, +]; + +type KeywordTypeMap = { + [name: string]: GetBlockSyncProgressType, +}; + +const keywordTypeMap: KeywordTypeMap = { + [blockKeyword]: 'replayedBlock', + [validatingChunkKeyword]: 'validatingChunk', + [validatedChunkKeyword]: 'validatingChunk', + [ledgerKeyword]: 'pushingLedger', +}; + +function containProgressKeywords(line: string) { + return progressKeywords.some((keyword) => line.includes(keyword)); +} + +function getProgressType(line: string): ?GetBlockSyncProgressType { + const key = progressKeywords.find((k) => line.includes(k)); + + if (!key) { + return null; + } + + return keywordTypeMap[key]; +} + export const handleCheckBlockReplayProgress = ( mainWindow: BrowserWindow, logsDirectoryPath: string @@ -20,19 +58,23 @@ export const handleCheckBlockReplayProgress = ( const rl = readline.createInterface({ input: fileStream }); const progress = []; for await (const line of rl) { - if (line.includes('block replay')) { + if (containProgressKeywords(line)) { progress.push(line); } } if (!progress.length) return; const finalProgress = progress.slice(-1).pop(); - const percentage = finalProgress.split('block replay progress (%) =').pop(); + const percentage = finalProgress.match(/Progress:([\s\d.,]+)%/)?.[1]; + const progressType = getProgressType(finalProgress); + if (!percentage || !progressType) { + return; + } const finalProgressPercentage = parseFloat(percentage); // Send result to renderer process (NetworkStatusStore) - getBlockReplayProgressChannel.send( - finalProgressPercentage, + getBlockSyncProgressChannel.send( + { progress: finalProgressPercentage, type: progressType }, mainWindow.webContents ); }; diff --git a/source/main/utils/installChromeExtensions.js b/source/main/utils/installChromeExtensions.js index e9e2e0ccd2..a2f23f8b5c 100644 --- a/source/main/utils/installChromeExtensions.js +++ b/source/main/utils/installChromeExtensions.js @@ -1,14 +1,20 @@ // @flow export const installChromeExtensions = async (isDev: boolean) => { if (isDev) { - const installer = require('electron-devtools-installer'); // eslint-disable-line global-require + const { + default: installExtension, + REACT_DEVELOPER_TOOLS, + } = require('electron-devtools-installer'); // eslint-disable-line global-require + const { app } = require('electron'); - const extensions = ['REACT_DEVELOPER_TOOLS']; - const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - for (const name of extensions) { - try { - await installer.default(installer[name], forceDownload); - } catch (e) {} // eslint-disable-line - } + const extensions = [REACT_DEVELOPER_TOOLS]; + const options = { + loadExtensionOptions: { allowFileAccess: true }, + }; + + try { + await app.whenReady(); + await installExtension(extensions, options); + } catch (e) {} // eslint-disable-line } }; diff --git a/source/main/utils/rtsFlagsSettings.js b/source/main/utils/rtsFlagsSettings.js new file mode 100644 index 0000000000..8a8d999bd0 --- /dev/null +++ b/source/main/utils/rtsFlagsSettings.js @@ -0,0 +1,31 @@ +// @flow +import Store from 'electron-store'; +import { logger } from './logging'; + +const store = new Store(); + +const getStoreKey = (network: string): string => `${network}-RTS-FLAGS`; + +export const getRtsFlagsSettings = (network: string): string[] | null => { + try { + const flags = store.get(getStoreKey(network)); + logger.info(`[RTS-FLAGS] Read ${network} flags: ${flags} from config`); + return flags; + } catch (error) { + logger.error( + `[RTS-FLAGS] Failed to read ${network} flags from config`, + error + ); + } + return null; +}; + +export const storeRtsFlagsSettings = ( + network: string, + flags: string[] +): void => { + logger.info( + `[RTS-FLAGS] Persisted ${network} flags: [${flags.toString()}] in config` + ); + store.set(getStoreKey(network), flags); +}; diff --git a/source/main/utils/safeExitWithCode.js b/source/main/utils/safeExitWithCode.js index ecccea68cd..80bc8483d1 100644 --- a/source/main/utils/safeExitWithCode.js +++ b/source/main/utils/safeExitWithCode.js @@ -13,3 +13,16 @@ export const safeExitWithCode = (exitCode: number = 0) => { app.exit(exitCode); }); }; + +export const relaunch = () => { + const { file } = log.transports; + // Prevent electron-log from writing to stream + file.level = false; + // Flush the stream to the log file and exit afterwards. + // https://nodejs.org/api/stream.html#stream_writable_end_chunk_encoding_callback + file.stream.end('', 'utf8', () => { + app.releaseSingleInstanceLock(); + app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) }); + app.exit(0); + }); +}; diff --git a/source/main/webpack.config.js b/source/main/webpack.config.js index a35fbc21ed..d4608f153b 100644 --- a/source/main/webpack.config.js +++ b/source/main/webpack.config.js @@ -60,6 +60,11 @@ module.exports = { }, }, }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, ], }, plugins: [ diff --git a/source/main/windows/main.js b/source/main/windows/main.js index e2a1ef0307..8e1286fe58 100644 --- a/source/main/windows/main.js +++ b/source/main/windows/main.js @@ -8,10 +8,12 @@ import { getTranslation } from '../utils/getTranslation'; import { getContentMinimumSize } from '../utils/getContentMinimumSize'; import { buildLabel, launcherConfig } from '../config'; import { ledgerStatus } from '../ipc/getHardwareWalletChannel'; +import { getRtsFlagsSettings } from '../utils/rtsFlagsSettings'; const rendererErrorHandler = new RendererErrorHandler(); -const { isDev, isTest, isLinux, isBlankScreenFixActive } = environment; +const { isDev, isTest, isLinux, isBlankScreenFixActive, network } = environment; +const rtsFlags = getRtsFlagsSettings(network); const id = 'window'; @@ -21,6 +23,8 @@ const getWindowTitle = (locale: string): string => { let title = buildLabel; if (isBlankScreenFixActive) title += ` ${translation('title.blankScreenFix')}`; + if (!!rtsFlags && rtsFlags?.length > 0) + title += ` ${translation('title.usingRtsFlags')}`; return title; }; diff --git a/source/renderer/app/App.js b/source/renderer/app/App.js index 883feb68ba..0f2f4bfa19 100755 --- a/source/renderer/app/App.js +++ b/source/renderer/app/App.js @@ -20,6 +20,8 @@ import { DIALOGS } from '../../common/ipc/constants'; import type { StoresMap } from './stores/index'; import type { ActionsMap } from './actions/index'; import NewsFeedContainer from './containers/news/NewsFeedContainer'; +import ToggleRTSFlagsDialogContainer from './containers/knownIssues/ToggleRTSFlagsDialogContainer'; +import RTSFlagsRecommendationOverlayContainer from './containers/knownIssues/RTSFlagsRecommendationOverlayContainer'; @observer export default class App extends Component<{ @@ -41,13 +43,17 @@ export default class App extends Component<{ const mobxDevTools = global.environment.mobxDevTools ? : null; const { currentTheme } = stores.profile; const themeVars = require(`./themes/daedalus/${currentTheme}.js`).default; - const { ABOUT, DAEDALUS_DIAGNOSTICS } = DIALOGS; + const { ABOUT, DAEDALUS_DIAGNOSTICS, TOGGLE_RTS_FLAGS_MODE } = DIALOGS; const canShowNews = !isSetupPage && // Active page is not "Language Selection" or "Terms of Use" !isNodeStopping && // Daedalus is not shutting down !isNodeStopped; // Daedalus is not shutting down + if (document.documentElement) { + document.documentElement.lang = locale; + } + return ( @@ -71,8 +77,12 @@ export default class App extends Component<{ isActiveDialog(DAEDALUS_DIAGNOSTICS) && ( ), - , + isActiveDialog(TOGGLE_RTS_FLAGS_MODE) && ( + + ), ]} + + {canShowNews && [ , , diff --git a/source/renderer/app/Routes.js b/source/renderer/app/Routes.js index be4d623025..caccb5b113 100644 --- a/source/renderer/app/Routes.js +++ b/source/renderer/app/Routes.js @@ -12,6 +12,7 @@ import WalletsSettingsPage from './containers/settings/categories/WalletsSetting import StakePoolsSettingsPage from './containers/settings/categories/StakePoolsSettingsPage'; import SupportSettingsPage from './containers/settings/categories/SupportSettingsPage'; import TermsOfUseSettingsPage from './containers/settings/categories/TermsOfUseSettingsPage'; +import SecuritySettingsPage from './containers/settings/categories/SecuritySettingsPage'; import TermsOfUsePage from './containers/profile/TermsOfUsePage'; import DataLayerMigrationPage from './containers/profile/DataLayerMigrationPage'; import DisplaySettingsPage from './containers/settings/categories/DisplaySettingsPage'; @@ -114,6 +115,10 @@ export const Routes = withRouter(() => ( path={ROUTES.SETTINGS.DISPLAY} component={DisplaySettingsPage} /> + = new Action(); openDaedalusDiagnosticsDialog: Action = new Action(); + + closeToggleRTSFlagsModeDialog: Action = new Action(); + openToggleRTSFlagsModeDialog: Action = new Action(); } diff --git a/source/renderer/app/actions/network-status-actions.js b/source/renderer/app/actions/network-status-actions.js index 81fd8cf1a3..74453d8847 100644 --- a/source/renderer/app/actions/network-status-actions.js +++ b/source/renderer/app/actions/network-status-actions.js @@ -10,4 +10,5 @@ export default class NetworkStatusActions { toggleSplash: Action = new Action(); copyStateDirectoryPath: Action = new Action(); forceCheckNetworkClock: Action = new Action(); + toggleRTSFlagsMode: Action = new Action(); } diff --git a/source/renderer/app/actions/profile-actions.js b/source/renderer/app/actions/profile-actions.js index 73cae4c230..3a13b49682 100644 --- a/source/renderer/app/actions/profile-actions.js +++ b/source/renderer/app/actions/profile-actions.js @@ -21,4 +21,5 @@ export default class ProfileActions { }> = new Action(); updateTheme: Action<{ theme: string }> = new Action(); finishInitialScreenSettings: Action = new Action(); + acknowledgeRTSModeRecommendation: Action = new Action(); } diff --git a/source/renderer/app/components/appUpdate/AppUpdateOverlay.scss b/source/renderer/app/components/appUpdate/AppUpdateOverlay.scss index c7cbde2bf5..44ca406396 100644 --- a/source/renderer/app/components/appUpdate/AppUpdateOverlay.scss +++ b/source/renderer/app/components/appUpdate/AppUpdateOverlay.scss @@ -141,7 +141,6 @@ &:after { border-color: var(--theme-news-overlay-update-background-color); - margin: -3px 0 0; } } diff --git a/source/renderer/app/components/assets/AssetAmount.js b/source/renderer/app/components/assets/AssetAmount.js index 24f6714445..367718d074 100644 --- a/source/renderer/app/components/assets/AssetAmount.js +++ b/source/renderer/app/components/assets/AssetAmount.js @@ -1,13 +1,14 @@ // @flow -import React, { Component } from 'react'; +import React from 'react'; import BigNumber from 'bignumber.js'; import classnames from 'classnames'; import { PopOver } from 'react-polymorph/lib/components/PopOver'; -import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; +import { defineMessages, FormattedHTMLMessage } from 'react-intl'; import { observer } from 'mobx-react'; +import { discreetWalletTokenAmount } from '../../features/discreet-mode/replacers/discreetWalletTokenAmount'; import styles from './AssetAmount.scss'; -import { formattedTokenWalletAmount } from '../../utils/formatters'; import type { AssetMetadata } from '../../api/assets/types'; +import { useDiscreetModeFeature } from '../../features/discreet-mode'; const messages = defineMessages({ unformattedAmount: { @@ -26,47 +27,57 @@ type Props = { isShort?: boolean, }; -@observer -export default class AssetAmount extends Component { - static contextTypes = { - intl: intlShape.isRequired, - }; +function AssetAmount({ + amount, + metadata, + decimals, + isLoading, + className, + isShort, +}: Props) { + const discreetModeFeature = useDiscreetModeFeature(); - render() { - const { - amount, - metadata, - decimals, - isLoading, - className, - isShort, - } = this.props; - if (isLoading) return '-'; - const componentStyles = classnames([styles.component, className]); - const content = !isLoading - ? formattedTokenWalletAmount(amount, metadata, decimals, isShort) - : '-'; - return ( -
- {decimals ? ( - - } - visible={decimals ? undefined : false} - className={styles.unformattedAmount} - > - {content} - - ) : ( - {content} - )} -
- ); - } + if (isLoading) return '-'; + const componentStyles = classnames([styles.component, className]); + const content = !isLoading + ? discreetModeFeature.discreetValue({ + replacer: discreetWalletTokenAmount({ + amount, + metadata, + decimals, + isShort, + }), + }) + : '-'; + + return ( +
+ {decimals ? ( + + } + visible={decimals ? undefined : false} + className={styles.unformattedAmount} + > + {content} + + ) : ( + {content} + )} +
+ ); } + +export default observer(AssetAmount); diff --git a/source/renderer/app/components/assets/AssetContent.scss b/source/renderer/app/components/assets/AssetContent.scss index 60f5c47cbf..6a436d801b 100644 --- a/source/renderer/app/components/assets/AssetContent.scss +++ b/source/renderer/app/components/assets/AssetContent.scss @@ -19,7 +19,7 @@ line-height: 1.33; text-align: right; white-space: nowrap; - width: 120px; + width: 70px; } dd { diff --git a/source/renderer/app/components/assets/AssetSettingsDialog.js b/source/renderer/app/components/assets/AssetSettingsDialog.js index 51d8e920f7..a5b4481708 100644 --- a/source/renderer/app/components/assets/AssetSettingsDialog.js +++ b/source/renderer/app/components/assets/AssetSettingsDialog.js @@ -12,12 +12,12 @@ import Dialog from '../widgets/Dialog'; import styles from './AssetSettingsDialog.scss'; import globalMessages from '../../i18n/global-messages'; import type { AssetToken } from '../../api/assets/types'; -import { formattedTokenWalletAmount } from '../../utils/formatters'; import warningIcon from '../../assets/images/asset-token-warning-ic.inline.svg'; import { DEFAULT_DECIMAL_PRECISION, MAX_DECIMAL_PRECISION, } from '../../config/assetsConfig'; +import { DiscreetTokenWalletAmount } from '../../features/discreet-mode'; const messages = defineMessages({ title: { @@ -180,16 +180,18 @@ export default class AssetSettingsDialog extends Component {
{intl.formatMessage(messages.unformattedBalanceLabel)}
-

{formattedTokenWalletAmount(asset.quantity, null, 0)}

+

+ +

{intl.formatMessage(messages.formattedBalanceLabel)}

- {formattedTokenWalletAmount( - asset.quantity, - asset.metadata, - decimals - )} +